diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts
index 0248cdf9b6e36..04cb76f4441c5 100644
--- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts
+++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts
@@ -10,6 +10,7 @@ import {
defaultErrorMessage,
buildMutedRulesFilter,
buildEntityFlyoutPreviewQuery,
+ buildEntityAlertsQuery,
} from './helpers';
const fallbackMessage = 'thisIsAFallBackMessage';
@@ -182,4 +183,78 @@ describe('test helper methods', () => {
expect(buildEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery);
});
});
+
+ describe('buildEntityAlertsQuery', () => {
+ const getExpectedAlertsQuery = (size?: number) => {
+ return {
+ size: size || 0,
+ _source: false,
+ fields: [
+ '_id',
+ '_index',
+ 'kibana.alert.rule.uuid',
+ 'kibana.alert.severity',
+ 'kibana.alert.rule.name',
+ 'kibana.alert.workflow_status',
+ ],
+ query: {
+ bool: {
+ filter: [
+ {
+ bool: {
+ must: [],
+ filter: [
+ {
+ match_phrase: {
+ 'host.name': {
+ query: 'exampleHost',
+ },
+ },
+ },
+ ],
+ should: [],
+ must_not: [],
+ },
+ },
+ {
+ range: {
+ '@timestamp': {
+ gte: 'Today',
+ lte: 'Tomorrow',
+ },
+ },
+ },
+ {
+ terms: {
+ 'kibana.alert.workflow_status': ['open', 'acknowledged'],
+ },
+ },
+ ],
+ },
+ },
+ };
+ };
+
+ it('should return the correct query when given all params', () => {
+ const field = 'host.name';
+ const query = 'exampleHost';
+ const to = 'Tomorrow';
+ const from = 'Today';
+ const size = 100;
+
+ expect(buildEntityAlertsQuery(field, to, from, query, size)).toEqual(
+ getExpectedAlertsQuery(size)
+ );
+ });
+
+ it('should return the correct query when not given size', () => {
+ const field = 'host.name';
+ const query = 'exampleHost';
+ const to = 'Tomorrow';
+ const from = 'Today';
+ const size = undefined;
+
+ expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size));
+ });
+ });
});
diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts
index 7039c99af6d53..bd531fa63804f 100644
--- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts
+++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
+
import { i18n } from '@kbn/i18n';
import type { CspBenchmarkRulesStates } from '../schema/rules/latest';
@@ -62,3 +63,59 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string
},
};
};
+
+export const buildEntityAlertsQuery = (
+ field: string,
+ to: string,
+ from: string,
+ queryValue?: string,
+ size?: number
+) => {
+ return {
+ size: size || 0,
+ _source: false,
+ fields: [
+ '_id',
+ '_index',
+ 'kibana.alert.rule.uuid',
+ 'kibana.alert.severity',
+ 'kibana.alert.rule.name',
+ 'kibana.alert.workflow_status',
+ ],
+ query: {
+ bool: {
+ filter: [
+ {
+ bool: {
+ must: [],
+ filter: [
+ {
+ match_phrase: {
+ [field]: {
+ query: queryValue,
+ },
+ },
+ },
+ ],
+ should: [],
+ must_not: [],
+ },
+ },
+ {
+ range: {
+ '@timestamp': {
+ gte: from,
+ lte: to,
+ },
+ },
+ },
+ {
+ terms: {
+ 'kibana.alert.workflow_status': ['open', 'acknowledged'],
+ },
+ },
+ ],
+ },
+ },
+ };
+};
diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts
index 40880b132537d..9bbaedf587dde 100644
--- a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts
+++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts
@@ -40,10 +40,11 @@ export const useMisconfigurationFindings = (options: UseCspOptions) => {
params: buildMisconfigurationsFindingsQuery(options, rulesStates!),
})
);
- if (!aggregations) throw new Error('expected aggregations to be defined');
+ if (!aggregations && options.ignore_unavailable === false)
+ throw new Error('expected aggregations to be defined');
return {
- count: getMisconfigurationAggregationCount(aggregations.count.buckets),
+ count: getMisconfigurationAggregationCount(aggregations?.count.buckets),
rows: hits.hits.map((finding) => ({
result: finding._source?.result,
rule: finding?._source?.rule,
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx
index e0199ab40168d..fff9450b6a1cb 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx
@@ -11,6 +11,9 @@ import { AlertsPreview } from './alerts_preview';
import { TestProviders } from '../../../common/mock/test_providers';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type { ParsedAlertsData } from '../../../overview/components/detection_response/alerts_by_status/types';
+import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
+import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
+import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
const mockAlertsData: ParsedAlertsData = {
open: {
@@ -29,9 +32,10 @@ const mockAlertsData: ParsedAlertsData = {
},
};
-jest.mock(
- '../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data'
-);
+// Mock hooks
+jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview');
+jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview');
+jest.mock('../../../entity_analytics/api/hooks/use_risk_score');
jest.mock('@kbn/expandable-flyout');
describe('AlertsPreview', () => {
@@ -39,6 +43,13 @@ describe('AlertsPreview', () => {
beforeEach(() => {
(useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel });
+ (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({
+ data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } },
+ });
+ (useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] });
+ (useMisconfigurationPreview as jest.Mock).mockReturnValue({
+ data: { count: { passed: 1, failed: 1 } },
+ });
});
afterEach(() => {
jest.clearAllMocks();
@@ -47,17 +58,17 @@ describe('AlertsPreview', () => {
it('renders', () => {
const { getByTestId } = render(
-
+
);
- expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleText')).toBeInTheDocument();
+ expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleLink')).toBeInTheDocument();
});
it('renders correct alerts number', () => {
const { getByTestId } = render(
-
+
);
@@ -67,7 +78,7 @@ describe('AlertsPreview', () => {
it('should render the correct number of distribution bar section based on the number of severities', () => {
const { queryAllByTestId } = render(
-
+
);
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx
index 3f9a0115d9ed1..c832f12c93f78 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx
@@ -5,19 +5,40 @@
* 2.0.
*/
-import React from 'react';
+import React, { useCallback, useMemo } from 'react';
import { capitalize } from 'lodash';
import type { EuiThemeComputed } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { DistributionBar } from '@kbn/security-solution-distribution-bar';
-import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common';
-import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
-import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
+import {
+ buildEntityFlyoutPreviewQuery,
+ getAbbreviatedNumber,
+} from '@kbn/cloud-security-posture-common';
+import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture';
+import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
+import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
+import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type {
AlertsByStatus,
ParsedAlertsData,
} from '../../../overview/components/detection_response/alerts_by_status/types';
+import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel';
+import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
+import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy';
+import {
+ buildHostNamesFilter,
+ buildUserNamesFilter,
+ RiskScoreEntity,
+} from '../../../../common/search_strategy';
+import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score';
+import { FIRST_RECORD_PAGINATION } from '../../../entity_analytics/common';
+import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left';
+import {
+ EntityDetailsLeftPanelTab,
+ CspInsightLeftPanelSubTab,
+} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
+import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left';
const AlertsCount = ({
alertsTotal,
@@ -56,9 +77,13 @@ const AlertsCount = ({
export const AlertsPreview = ({
alertsData,
+ fieldName,
+ name,
isPreviewMode,
}: {
alertsData: ParsedAlertsData;
+ fieldName: string;
+ name: string;
isPreviewMode?: boolean;
}) => {
const { euiTheme } = useEuiTheme();
@@ -82,9 +107,120 @@ export const AlertsPreview = ({
const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0);
+ const { data } = useMisconfigurationPreview({
+ query: buildEntityFlyoutPreviewQuery(fieldName, name),
+ sort: [],
+ enabled: true,
+ pageSize: 1,
+ ignore_unavailable: true,
+ });
+ const isUsingHostName = fieldName === 'host.name';
+ const passedFindings = data?.count.passed || 0;
+ const failedFindings = data?.count.failed || 0;
+
+ const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
+
+ const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({
+ query: buildEntityFlyoutPreviewQuery('host.name', name),
+ sort: [],
+ enabled: true,
+ pageSize: 1,
+ });
+
+ const {
+ CRITICAL = 0,
+ HIGH = 0,
+ MEDIUM = 0,
+ LOW = 0,
+ NONE = 0,
+ } = vulnerabilitiesData?.count || {};
+
+ const hasVulnerabilitiesFindings = hasVulnerabilitiesData({
+ critical: CRITICAL,
+ high: HIGH,
+ medium: MEDIUM,
+ low: LOW,
+ none: NONE,
+ });
+
+ const buildFilterQuery = useMemo(
+ () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])),
+ [isUsingHostName, name]
+ );
+
+ const riskScoreState = useRiskScore({
+ riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user,
+ filterQuery: buildFilterQuery,
+ onlyLatest: false,
+ pagination: FIRST_RECORD_PAGINATION,
+ });
+
+ const { data: hostRisk } = riskScoreState;
+
+ const riskData = hostRisk?.[0];
+
+ const isRiskScoreExist = isUsingHostName
+ ? !!(riskData as HostRiskScore)?.host.risk
+ : !!(riskData as UserRiskScore)?.user.risk;
+
+ const hasNonClosedAlerts = totalAlertsCount > 0;
+
+ const { openLeftPanel } = useExpandableFlyoutApi();
+
+ const goToEntityInsightTab = useCallback(() => {
+ openLeftPanel({
+ id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey,
+ params: isUsingHostName
+ ? {
+ name,
+ isRiskScoreExist,
+ hasMisconfigurationFindings,
+ hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
+ path: {
+ tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
+ subTab: CspInsightLeftPanelSubTab.ALERTS,
+ },
+ }
+ : {
+ user: { name },
+ isRiskScoreExist,
+ hasMisconfigurationFindings,
+ hasNonClosedAlerts,
+ path: {
+ tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
+ subTab: CspInsightLeftPanelSubTab.ALERTS,
+ },
+ },
+ });
+ }, [
+ hasMisconfigurationFindings,
+ hasNonClosedAlerts,
+ hasVulnerabilitiesFindings,
+ isRiskScoreExist,
+ isUsingHostName,
+ name,
+ openLeftPanel,
+ ]);
+ const link = useMemo(
+ () =>
+ !isPreviewMode
+ ? {
+ callback: goToEntityInsightTab,
+ tooltip: (
+
+ ),
+ }
+ : undefined,
+ [isPreviewMode, goToEntityInsightTab]
+ );
return (
),
+ link: totalAlertsCount > 0 ? link : undefined,
}}
data-test-subj={'securitySolutionFlyoutInsightsAlerts'}
>
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx
new file mode 100644
index 0000000000000..966de68e3497f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx
@@ -0,0 +1,265 @@
+/*
+ * 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, { memo, useCallback, useEffect, useState } from 'react';
+import { capitalize } from 'lodash';
+import type { Criteria, EuiBasicTableColumn } from '@elastic/eui';
+import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { DistributionBar } from '@kbn/security-solution-distribution-bar';
+import {
+ ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS,
+ uiMetricService,
+} from '@kbn/cloud-security-posture-common/utils/ui_metrics';
+import { METRIC_TYPE } from '@kbn/analytics';
+import { buildEntityAlertsQuery } from '@kbn/cloud-security-posture-common/utils/helpers';
+import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
+import { TableId } from '@kbn/securitysolution-data-table';
+import {
+ OPEN_IN_ALERTS_TITLE_HOSTNAME,
+ OPEN_IN_ALERTS_TITLE_STATUS,
+ OPEN_IN_ALERTS_TITLE_USERNAME,
+} from '../../../overview/components/detection_response/translations';
+import { useNavigateToAlertsPageWithFilters } from '../../../common/hooks/use_navigate_to_alerts_page_with_filters';
+import { DocumentDetailsPreviewPanelKey } from '../../../flyout/document_details/shared/constants/panel_keys';
+import { useGlobalTime } from '../../../common/containers/use_global_time';
+import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query';
+import { ALERTS_QUERY_NAMES } from '../../../detections/containers/detection_engine/alerts/constants';
+import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
+import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers';
+import { SeverityBadge } from '../../../common/components/severity_badge';
+import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/constants';
+import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types';
+
+type AlertSeverity = 'low' | 'medium' | 'high' | 'critical';
+
+interface ResultAlertsField {
+ _id: string[];
+ _index: string[];
+ 'kibana.alert.rule.uuid': string[];
+ 'kibana.alert.severity': AlertSeverity[];
+ 'kibana.alert.rule.name': string[];
+ 'kibana.alert.workflow_status': string[];
+}
+
+interface ContextualFlyoutAlertsField {
+ id: string;
+ index: string;
+ ruleUuid: string;
+ ruleName: string;
+ severity: AlertSeverity;
+ status: string;
+}
+
+interface AlertsDetailsFields {
+ fields: ResultAlertsField;
+}
+
+export const AlertsDetailsTable = memo(
+ ({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => {
+ useEffect(() => {
+ uiMetricService.trackUiMetric(
+ METRIC_TYPE.COUNT,
+ ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS
+ );
+ }, []);
+
+ const [pageIndex, setPageIndex] = useState(0);
+ const [pageSize, setPageSize] = useState(10);
+
+ const alertsPagination = (alerts: ContextualFlyoutAlertsField[]) => {
+ let pageOfItems;
+
+ if (!pageIndex && !pageSize) {
+ pageOfItems = alerts;
+ } else {
+ const startIndex = pageIndex * pageSize;
+ pageOfItems = alerts?.slice(startIndex, Math.min(startIndex + pageSize, alerts?.length));
+ }
+
+ return {
+ pageOfItems,
+ totalItemCount: alerts?.length,
+ };
+ };
+
+ const { to, from } = useGlobalTime();
+ const { signalIndexName } = useSignalIndex();
+ const { data } = useQueryAlerts({
+ query: buildEntityAlertsQuery(fieldName, to, from, queryName, 500),
+ queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS,
+ indexName: signalIndexName,
+ });
+
+ const alertDataResults = (data?.hits?.hits as AlertsDetailsFields[])?.map(
+ (item: AlertsDetailsFields) => {
+ return {
+ id: item.fields?._id?.[0],
+ index: item.fields?._index?.[0],
+ ruleName: item.fields?.['kibana.alert.rule.name']?.[0],
+ ruleUuid: item.fields?.['kibana.alert.rule.uuid']?.[0],
+ severity: item.fields?.['kibana.alert.severity']?.[0],
+ status: item.fields?.['kibana.alert.workflow_status']?.[0],
+ };
+ }
+ );
+
+ const severitiesMap = alertDataResults?.map((item) => item.severity) || [];
+
+ const alertStats = Object.entries(
+ severitiesMap.reduce((acc: Record, item) => {
+ acc[item] = (acc[item] || 0) + 1;
+ return acc;
+ }, {})
+ ).map(([key, count]) => ({
+ key: capitalize(key),
+ count,
+ color: getSeverityColor(key),
+ }));
+
+ const { pageOfItems, totalItemCount } = alertsPagination(alertDataResults || []);
+
+ const pagination = {
+ pageIndex,
+ pageSize,
+ totalItemCount,
+ pageSizeOptions: [10, 25, 100],
+ };
+
+ const onTableChange = ({ page }: Criteria) => {
+ if (page) {
+ const { index, size } = page;
+ setPageIndex(index);
+ setPageSize(size);
+ }
+ };
+
+ const { openPreviewPanel } = useExpandableFlyoutApi();
+
+ const handleOnEventAlertDetailPanelOpened = useCallback(
+ (eventId: string, indexName: string, tableId: string) => {
+ openPreviewPanel({
+ id: DocumentDetailsPreviewPanelKey,
+ params: {
+ id: eventId,
+ indexName,
+ scopeId: tableId,
+ isPreviewMode: true,
+ banner: ALERT_PREVIEW_BANNER,
+ },
+ });
+ },
+ [openPreviewPanel]
+ );
+
+ const tableId = TableId.alertsOnRuleDetailsPage;
+
+ const columns: Array> = [
+ {
+ field: 'id',
+ name: '',
+ width: '5%',
+ render: (id: string, alert: ContextualFlyoutAlertsField) => (
+ handleOnEventAlertDetailPanelOpened(id, alert.index, tableId)}>
+
+
+ ),
+ },
+ {
+ field: 'ruleName',
+ render: (ruleName: string) => {ruleName},
+ name: i18n.translate(
+ 'xpack.securitySolution.flyout.left.insights.alerts.table.ruleNameColumnName',
+ {
+ defaultMessage: 'Rule',
+ }
+ ),
+ width: '55%',
+ },
+ {
+ field: 'severity',
+ render: (severity: AlertSeverity) => (
+
+
+
+ ),
+ name: i18n.translate(
+ 'xpack.securitySolution.flyout.left.insights.alerts.table.severityColumnName',
+ {
+ defaultMessage: 'Severity',
+ }
+ ),
+ width: '20%',
+ },
+ {
+ field: 'status',
+ render: (status: string) => {capitalize(status)},
+ name: i18n.translate(
+ 'xpack.securitySolution.flyout.left.insights.alerts.table.statusColumnName',
+ {
+ defaultMessage: 'Status',
+ }
+ ),
+ width: '20%',
+ },
+ ];
+
+ const openAlertsPageWithFilters = useNavigateToAlertsPageWithFilters();
+
+ const openAlertsInAlertsPage = useCallback(
+ () =>
+ openAlertsPageWithFilters(
+ [
+ {
+ title:
+ fieldName === 'host.name'
+ ? OPEN_IN_ALERTS_TITLE_HOSTNAME
+ : OPEN_IN_ALERTS_TITLE_USERNAME,
+ selectedOptions: [queryName],
+ fieldName,
+ },
+ {
+ title: OPEN_IN_ALERTS_TITLE_STATUS,
+ selectedOptions: [FILTER_OPEN, FILTER_ACKNOWLEDGED],
+ fieldName: 'kibana.alert.workflow_status',
+ },
+ ],
+ true
+ ),
+ [fieldName, openAlertsPageWithFilters, queryName]
+ );
+
+ return (
+ <>
+
+ openAlertsInAlertsPage()}>
+
+ {i18n.translate('xpack.securitySolution.flyout.left.insights.alerts.tableTitle', {
+ defaultMessage: 'Alerts ',
+ })}
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+);
+
+AlertsDetailsTable.displayName = 'AlertsDetailsTable';
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx
index 05421cfa7a208..2e7b4171fd023 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx
@@ -12,10 +12,10 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout';
import { useExpandableFlyoutState } from '@kbn/expandable-flyout';
import { i18n } from '@kbn/i18n';
-// import type { FlyoutPanels } from '@kbn/expandable-flyout/src/store/state';
import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header';
import { MisconfigurationFindingsDetailsTable } from './misconfiguration_findings_details_table';
import { VulnerabilitiesFindingsDetailsTable } from './vulnerabilities_findings_details_table';
+import { AlertsDetailsTable } from './alerts_findings_details_table';
/**
* Insights view displayed in the document details expandable flyout left section
@@ -26,6 +26,7 @@ interface CspFlyoutPanelProps extends FlyoutPanelProps {
path: PanelPath;
hasMisconfigurationFindings: boolean;
hasVulnerabilitiesFindings: boolean;
+ hasNonClosedAlerts: boolean;
};
}
@@ -35,7 +36,8 @@ function isCspFlyoutPanelProps(
): panelLeft is CspFlyoutPanelProps {
return (
!!panelLeft?.params?.hasMisconfigurationFindings ||
- !!panelLeft?.params?.hasVulnerabilitiesFindings
+ !!panelLeft?.params?.hasVulnerabilitiesFindings ||
+ !!panelLeft?.params?.hasNonClosedAlerts
);
}
@@ -45,12 +47,14 @@ export const InsightsTabCsp = memo(
let hasMisconfigurationFindings = false;
let hasVulnerabilitiesFindings = false;
+ let hasNonClosedAlerts = false;
let subTab: string | undefined;
// Check if panels.left is of type CspFlyoutPanelProps and extract values
if (isCspFlyoutPanelProps(panels.left)) {
hasMisconfigurationFindings = panels.left.params.hasMisconfigurationFindings;
hasVulnerabilitiesFindings = panels.left.params.hasVulnerabilitiesFindings;
+ hasNonClosedAlerts = panels.left.params.hasNonClosedAlerts;
subTab = panels.left.params.path?.subTab;
}
@@ -63,6 +67,8 @@ export const InsightsTabCsp = memo(
? CspInsightLeftPanelSubTab.MISCONFIGURATIONS
: hasVulnerabilitiesFindings
? CspInsightLeftPanelSubTab.VULNERABILITIES
+ : hasNonClosedAlerts
+ ? CspInsightLeftPanelSubTab.ALERTS
: '';
};
@@ -71,6 +77,19 @@ export const InsightsTabCsp = memo(
const insightsButtons: EuiButtonGroupOptionProps[] = useMemo(() => {
const buttons: EuiButtonGroupOptionProps[] = [];
+ if (panels.left?.params?.hasNonClosedAlerts) {
+ buttons.push({
+ id: CspInsightLeftPanelSubTab.ALERTS,
+ label: (
+
+ ),
+ 'data-test-subj': 'alertsTabDataTestId',
+ });
+ }
+
if (panels.left?.params?.hasMisconfigurationFindings) {
buttons.push({
id: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
@@ -96,9 +115,11 @@ export const InsightsTabCsp = memo(
'data-test-subj': 'vulnerabilitiesTabDataTestId',
});
}
+
return buttons;
}, [
panels.left?.params?.hasMisconfigurationFindings,
+ panels.left?.params?.hasNonClosedAlerts,
panels.left?.params?.hasVulnerabilitiesFindings,
]);
@@ -130,8 +151,10 @@ export const InsightsTabCsp = memo(
{activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? (
- ) : (
+ ) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? (
+ ) : (
+
)}
>
);
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx
index a43b56876f1ab..7139994f7e972 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx
@@ -94,7 +94,12 @@ export const EntityInsight = ({
if (alertsCount > 0) {
insightContent.push(
<>
-
+
>
);
@@ -103,14 +108,23 @@ export const EntityInsight = ({
if (hasMisconfigurationFindings)
insightContent.push(
<>
-
+ 0}
+ isPreviewMode={isPreviewMode}
+ />
>
);
if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)
insightContent.push(
<>
-
+ 0}
+ />
>
);
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx
index b133e9db22050..42a5906ce4e36 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx
@@ -103,10 +103,12 @@ const MisconfigurationPreviewScore = ({
export const MisconfigurationsPreview = ({
name,
fieldName,
+ hasNonClosedAlerts = false,
isPreviewMode,
}: {
name: string;
fieldName: 'host.name' | 'user.name';
+ hasNonClosedAlerts?: boolean;
isPreviewMode?: boolean;
}) => {
const { data } = useMisconfigurationPreview({
@@ -180,6 +182,7 @@ export const MisconfigurationsPreview = ({
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
path: {
tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
@@ -189,11 +192,16 @@ export const MisconfigurationsPreview = ({
user: { name },
isRiskScoreExist,
hasMisconfigurationFindings,
- path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS },
+ hasNonClosedAlerts,
+ path: {
+ tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS,
+ subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS,
+ },
},
});
}, [
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
hasVulnerabilitiesFindings,
isRiskScoreExist,
isUsingHostName,
diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx
index a9ddaff62085b..c4335d921e371 100644
--- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx
+++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx
@@ -72,9 +72,11 @@ const VulnerabilitiesCount = ({
export const VulnerabilitiesPreview = ({
name,
isPreviewMode,
+ hasNonClosedAlerts = false,
}: {
name: string;
isPreviewMode?: boolean;
+ hasNonClosedAlerts?: boolean;
}) => {
useEffect(() => {
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW);
@@ -132,11 +134,13 @@ export const VulnerabilitiesPreview = ({
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: 'vulnerabilitiesTabId' },
},
});
}, [
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
hasVulnerabilitiesFindings,
isRiskScoreExist,
name,
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts
index 6a957318f278f..3bfc0c56e81fa 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts
@@ -32,6 +32,7 @@ describe('useNavigateToAlertsPageWithFilters', () => {
expect(mockNavigateTo).toHaveBeenCalledWith({
deepLinkId: SecurityPageName.alerts,
path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field',hideActionBar:!f,selectedOptions:!('test value'),title:'test filter'))",
+ openInNewTab: false,
});
});
@@ -63,6 +64,7 @@ describe('useNavigateToAlertsPageWithFilters', () => {
expect(mockNavigateTo).toHaveBeenCalledWith({
deepLinkId: SecurityPageName.alerts,
path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field 1',hideActionBar:!f,selectedOptions:!('test value 1'),title:'test filter 1'),(exclude:!t,existsSelected:!t,fieldName:'test field 2',hideActionBar:!t,selectedOptions:!('test value 2'),title:'test filter 2'))",
+ openInNewTab: false,
});
});
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts
index fffa65797b3f8..037bac32d8c92 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts
@@ -16,7 +16,7 @@ import { URL_PARAM_KEY } from './use_url_state';
export const useNavigateToAlertsPageWithFilters = () => {
const { navigateTo } = useNavigation();
- return (filterItems: FilterControlConfig | FilterControlConfig[]) => {
+ return (filterItems: FilterControlConfig | FilterControlConfig[], openInNewTab = false) => {
const urlFilterParams = encode(
formatPageFilterSearchParam(Array.isArray(filterItems) ? filterItems : [filterItems])
);
@@ -24,6 +24,7 @@ export const useNavigateToAlertsPageWithFilters = () => {
navigateTo({
deepLinkId: SecurityPageName.alerts,
path: `?${URL_PARAM_KEY.pageFilter}=${urlFilterParams}`,
+ openInNewTab,
});
};
};
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx
index 6e5774ba1756e..107cb83ddd97b 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx
@@ -25,6 +25,7 @@ export interface HostDetailsPanelProps extends Record {
scopeId: string;
hasMisconfigurationFindings?: boolean;
hasVulnerabilitiesFindings?: boolean;
+ hasNonClosedAlerts?: boolean;
path?: {
tab?: EntityDetailsLeftPanelTab;
subTab?: CspInsightLeftPanelSubTab;
@@ -43,6 +44,7 @@ export const HostDetailsPanel = ({
path,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
}: HostDetailsPanelProps) => {
const [selectedTabId, setSelectedTabId] = useState(
path?.tab === EntityDetailsLeftPanelTab.CSP_INSIGHTS
@@ -58,11 +60,18 @@ export const HostDetailsPanel = ({
// Determine if the Insights tab should be included
const insightsTab =
- hasMisconfigurationFindings || hasVulnerabilitiesFindings
+ hasMisconfigurationFindings || hasVulnerabilitiesFindings || hasNonClosedAlerts
? [getInsightsInputTab({ name, fieldName: 'host.name' })]
: [];
return [[...riskScoreTab, ...insightsTab], EntityDetailsLeftPanelTab.RISK_INPUTS, () => {}];
- }, [isRiskScoreExist, name, scopeId, hasMisconfigurationFindings, hasVulnerabilitiesFindings]);
+ }, [
+ isRiskScoreExist,
+ name,
+ scopeId,
+ hasMisconfigurationFindings,
+ hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
+ ]);
return (
<>
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
index 83fa75474a1cc..a7e99898606f8 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx
@@ -13,6 +13,8 @@ import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-commo
import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview';
import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview';
import { sum } from 'lodash';
+import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
+import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id';
import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab';
import type { Refetch } from '../../../common/types';
@@ -35,6 +37,7 @@ import { useObservedHost } from './hooks/use_observed_host';
import { HostDetailsPanelKey } from '../host_details_left';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { HostPreviewPanelFooter } from '../host_preview/footer';
+import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface HostPanelProps extends Record {
@@ -120,6 +123,21 @@ export const HostPanel = ({
const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0;
+ const { signalIndexName } = useSignalIndex();
+
+ const entityFilter = useMemo(() => ({ field: 'host.name', value: hostName }), [hostName]);
+
+ const { items: alertsData } = useAlertsByStatus({
+ entityFilter,
+ signalIndexName,
+ queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`,
+ to,
+ from,
+ });
+
+ const hasNonClosedAlerts =
+ (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
+
useQueryInspector({
deleteQuery,
inspect: inspectRiskScore,
@@ -144,6 +162,7 @@ export const HostPanel = ({
path: tab ? { tab } : undefined,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
},
});
},
@@ -155,6 +174,7 @@ export const HostPanel = ({
isRiskScoreExist,
hasMisconfigurationFindings,
hasVulnerabilitiesFindings,
+ hasNonClosedAlerts,
]
);
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx
index 08623c941ba67..254985b865840 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx
@@ -28,6 +28,7 @@ export enum EntityDetailsLeftPanelTab {
export enum CspInsightLeftPanelSubTab {
MISCONFIGURATIONS = 'misconfigurationTabId',
VULNERABILITIES = 'vulnerabilitiesTabId',
+ ALERTS = 'alertsTabId',
}
export interface PanelHeaderProps {
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx
index 8e6cf3a9ee9d2..87c9e5abc7afd 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx
@@ -29,6 +29,7 @@ export interface UserDetailsPanelProps extends Record {
path?: PanelPath;
scopeId: string;
hasMisconfigurationFindings?: boolean;
+ hasNonClosedAlerts?: boolean;
}
export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps {
key: 'user_details';
@@ -42,6 +43,7 @@ export const UserDetailsPanel = ({
path,
scopeId,
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
}: UserDetailsPanelProps) => {
const managedUser = useManagedUser(user.name, user.email);
const tabs = useTabs(
@@ -49,7 +51,8 @@ export const UserDetailsPanel = ({
user.name,
isRiskScoreExist,
scopeId,
- hasMisconfigurationFindings
+ hasMisconfigurationFindings,
+ hasNonClosedAlerts
);
const { selectedTabId, setSelectedTabId } = useSelectedTab(
@@ -57,7 +60,8 @@ export const UserDetailsPanel = ({
user,
tabs,
path,
- hasMisconfigurationFindings
+ hasMisconfigurationFindings,
+ hasNonClosedAlerts
);
if (managedUser.isLoading) return ;
@@ -83,7 +87,8 @@ const useSelectedTab = (
user: UserParam,
tabs: LeftPanelTabsType,
path: PanelPath | undefined,
- hasMisconfigurationFindings?: boolean
+ hasMisconfigurationFindings?: boolean,
+ hasNonClosedAlerts?: boolean
) => {
const { openLeftPanel } = useExpandableFlyoutApi();
@@ -101,6 +106,7 @@ const useSelectedTab = (
user,
isRiskScoreExist,
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
path: {
tab: tabId,
},
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx
index 6f27b054759f2..0c1cdcaa904a9 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx
@@ -30,7 +30,8 @@ export const useTabs = (
name: string,
isRiskScoreExist: boolean,
scopeId: string,
- hasMisconfigurationFindings?: boolean
+ hasMisconfigurationFindings?: boolean,
+ hasNonClosedAlerts?: boolean
): LeftPanelTabsType =>
useMemo(() => {
const tabs: LeftPanelTabsType = [];
@@ -55,12 +56,19 @@ export const useTabs = (
tabs.push(getEntraTab(entraManagedUser));
}
- if (hasMisconfigurationFindings) {
+ if (hasMisconfigurationFindings || hasNonClosedAlerts) {
tabs.push(getInsightsInputTab({ name, fieldName: 'user.name' }));
}
return tabs;
- }, [hasMisconfigurationFindings, isRiskScoreExist, managedUser, name, scopeId]);
+ }, [
+ hasMisconfigurationFindings,
+ hasNonClosedAlerts,
+ isRiskScoreExist,
+ managedUser,
+ name,
+ scopeId,
+ ]);
const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({
id: EntityDetailsLeftPanelTab.OKTA,
diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
index 42c8664b2ac0c..07762ed9aea0c 100644
--- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx
@@ -33,6 +33,9 @@ import { UserDetailsPanelKey } from '../user_details_left';
import { useObservedUser } from './hooks/use_observed_user';
import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header';
import { UserPreviewPanelFooter } from '../user_preview/footer';
+import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
+import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status';
+import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types';
import { EntityEventTypes } from '../../../common/lib/telemetry';
export interface UserPanelProps extends Record {
@@ -112,6 +115,21 @@ export const UserPanel = ({
const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0;
+ const { signalIndexName } = useSignalIndex();
+
+ const entityFilter = useMemo(() => ({ field: 'user.name', value: userName }), [userName]);
+
+ const { items: alertsData } = useAlertsByStatus({
+ entityFilter,
+ signalIndexName,
+ queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`,
+ to,
+ from,
+ });
+
+ const hasNonClosedAlerts =
+ (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0;
+
useQueryInspector({
deleteQuery,
inspect,
@@ -139,6 +157,7 @@ export const UserPanel = ({
},
path: tab ? { tab } : undefined,
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
},
});
},
@@ -150,6 +169,7 @@ export const UserPanel = ({
userName,
email,
hasMisconfigurationFindings,
+ hasNonClosedAlerts,
]
);
const openPanelFirstTab = useCallback(
@@ -191,7 +211,8 @@ export const UserPanel = ({
<>