mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cloud Security] CNVM dashboard statistics (#158652)
This commit is contained in:
parent
e0b8304c8a
commit
015ae200e2
18 changed files with 342 additions and 48 deletions
|
@ -5,10 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PostureTypes } from './types';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { PostureTypes, VulnSeverity } from './types';
|
||||
|
||||
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
|
||||
export const STATS_ROUTE_PATH = '/internal/cloud_security_posture/stats/{policy_template}';
|
||||
export const VULNERABILITIES_DASHBOARD_ROUTE_PATH =
|
||||
'/internal/cloud_security_posture/vulnerabilities_dashboard';
|
||||
export const BENCHMARKS_ROUTE_PATH = '/internal/cloud_security_posture/benchmarks';
|
||||
export const FIND_CSP_RULE_TEMPLATE_ROUTE_PATH = '/internal/cloud_security_posture/rules/_find';
|
||||
|
||||
|
@ -102,3 +105,30 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = {
|
|||
|
||||
export const VULNERABILITIES = 'vulnerabilities';
|
||||
export const CONFIGURATIONS = 'configurations';
|
||||
|
||||
export const getSafeVulnerabilitiesQueryFilter = (query?: QueryDslQueryContainer) => ({
|
||||
...query,
|
||||
bool: {
|
||||
...query?.bool,
|
||||
filter: [
|
||||
...((query?.bool?.filter as []) || []),
|
||||
{ exists: { field: 'vulnerability.score.base' } },
|
||||
{ exists: { field: 'vulnerability.score.version' } },
|
||||
{ exists: { field: 'vulnerability.severity' } },
|
||||
{ exists: { field: 'resource.name' } },
|
||||
{ match_phrase: { 'vulnerability.enumeration': 'CVE' } },
|
||||
],
|
||||
must_not: [
|
||||
...((query?.bool?.must_not as []) || []),
|
||||
{ match_phrase: { 'vulnerability.severity': 'UNKNOWN' } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const SEVERITY: Record<VulnSeverity, VulnSeverity> = {
|
||||
LOW: 'LOW',
|
||||
MEDIUM: 'MEDIUM',
|
||||
HIGH: 'HIGH',
|
||||
CRITICAL: 'CRITICAL',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
};
|
||||
|
|
|
@ -125,3 +125,19 @@ export interface GetCspRuleTemplateResponse {
|
|||
page: number;
|
||||
perPage: number;
|
||||
}
|
||||
|
||||
// CNVM DASHBOARD
|
||||
|
||||
export interface CnvmStatistics {
|
||||
criticalCount: number | undefined;
|
||||
highCount: number | undefined;
|
||||
mediumCount: number | undefined;
|
||||
resourcesScanned: number | undefined;
|
||||
cloudRegions: number | undefined;
|
||||
}
|
||||
|
||||
export interface CnvmDashboardData {
|
||||
cnvmStatistics: CnvmStatistics;
|
||||
}
|
||||
|
||||
export type VulnSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' | 'UNKNOWN';
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { CnvmDashboardData } from '../../../common/types';
|
||||
import { useKibana } from '../hooks/use_kibana';
|
||||
import { VULNERABILITIES_DASHBOARD_ROUTE_PATH } from '../../../common/constants';
|
||||
|
||||
const cnvmKey = 'use-cnvm-statistics-api-key';
|
||||
|
||||
export const useCnvmStatisticsApi = (
|
||||
options?: UseQueryOptions<unknown, unknown, CnvmDashboardData, string[]>
|
||||
) => {
|
||||
const { http } = useKibana().services;
|
||||
return useQuery(
|
||||
[cnvmKey],
|
||||
() => http.get<CnvmDashboardData>(VULNERABILITIES_DASHBOARD_ROUTE_PATH),
|
||||
options
|
||||
);
|
||||
};
|
|
@ -68,3 +68,6 @@ export const useNavigateFindings = () => useNavigate(findingsNavigation.findings
|
|||
|
||||
export const useNavigateFindingsByResource = () =>
|
||||
useNavigate(findingsNavigation.findings_by_resource.path);
|
||||
|
||||
export const useNavigateVulnerabilities = () =>
|
||||
useNavigate(findingsNavigation.vulnerabilities.path);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { VulnSeverity } from '../../../common/types';
|
||||
|
||||
export const getCvsScoreColor = (score: number): string | undefined => {
|
||||
if (score <= 4) {
|
||||
|
@ -19,7 +20,7 @@ export const getCvsScoreColor = (score: number): string | undefined => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getSeverityStatusColor = (severity: string): string | undefined => {
|
||||
export const getSeverityStatusColor = (severity: VulnSeverity): string => {
|
||||
switch (severity) {
|
||||
case 'LOW':
|
||||
return euiThemeVars.euiColorVis0;
|
||||
|
|
|
@ -6,15 +6,26 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CompactFormattedNumber = ({
|
||||
number = 0,
|
||||
number,
|
||||
abbreviateAbove = 999999,
|
||||
}: {
|
||||
number: number;
|
||||
number?: number;
|
||||
/** numbers higher than the value of this field will be abbreviated using compact notation and have a tooltip displaying the full value */
|
||||
abbreviateAbove?: number;
|
||||
}) => {
|
||||
if (!number && number !== 0) {
|
||||
return (
|
||||
<span>
|
||||
{i18n.translate('xpack.csp.compactFormattedNumber.naTitle', {
|
||||
defaultMessage: 'N/A',
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (number <= abbreviateAbove) {
|
||||
return <span>{number.toLocaleString()}</span>;
|
||||
}
|
||||
|
|
|
@ -18,17 +18,6 @@ export interface CspCounterCardProps {
|
|||
description: EuiStatProps['description'];
|
||||
}
|
||||
|
||||
// Todo: remove when EuiIcon type="pivot" is available
|
||||
const PivotIcon = ({ ...props }) => (
|
||||
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M2.89 13.847 11.239 5.5a.522.522 0 0 0-.737-.737L2.154 13.11a.522.522 0 0 0 .738.738ZM14 6.696a.522.522 0 1 1-1.043 0v-3.13a.522.522 0 0 0-.522-.523h-3.13a.522.522 0 1 1 0-1.043h3.13C13.299 2 14 2.7 14 3.565v3.13Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CspCounterCard = (counter: CspCounterCardProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
|
@ -68,8 +57,7 @@ export const CspCounterCard = (counter: CspCounterCardProps) => {
|
|||
/>
|
||||
{counter.onClick && (
|
||||
<EuiIcon
|
||||
// Todo: update when EuiIcon type="pivot" is available
|
||||
type={PivotIcon}
|
||||
type={'pivot'}
|
||||
css={css`
|
||||
color: ${euiTheme.colors.lightShade};
|
||||
position: absolute;
|
||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import { css } from '@emotion/react';
|
||||
import { float } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getCvsScoreColor, getSeverityStatusColor } from '../common/utils/get_vulnerability_colors';
|
||||
import { VulnSeverity } from '../../common/types';
|
||||
|
||||
interface CVSScoreBadgeProps {
|
||||
score: float;
|
||||
|
@ -17,7 +18,7 @@ interface CVSScoreBadgeProps {
|
|||
}
|
||||
|
||||
interface SeverityStatusBadgeProps {
|
||||
status: string;
|
||||
severity: VulnSeverity;
|
||||
}
|
||||
|
||||
export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
|
||||
|
@ -53,8 +54,8 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const SeverityStatusBadge = ({ status }: SeverityStatusBadgeProps) => {
|
||||
const color = getSeverityStatusColor(status);
|
||||
export const SeverityStatusBadge = ({ severity }: SeverityStatusBadgeProps) => {
|
||||
const color = getSeverityStatusColor(severity);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -68,10 +69,10 @@ export const SeverityStatusBadge = ({ status }: SeverityStatusBadgeProps) => {
|
|||
type="dot"
|
||||
color={color}
|
||||
css={css`
|
||||
opacity: ${status ? 1 : 0};
|
||||
opacity: ${severity ? 1 : 0};
|
||||
`}
|
||||
/>
|
||||
{status}
|
||||
{severity}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -58,9 +58,9 @@ describe('<CloudSummarySection />', () => {
|
|||
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('1M');
|
||||
});
|
||||
|
||||
it('renders 0 as empty state', () => {
|
||||
it('renders N/A as an empty state', () => {
|
||||
renderCloudSummarySection({ stats: { totalFailed: undefined } });
|
||||
|
||||
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('0');
|
||||
expect(screen.getByTestId(DASHBOARD_COUNTER_CARDS.FAILING_FINDINGS)).toHaveTextContent('N/A');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,10 @@ import { lastValueFrom } from 'rxjs';
|
|||
import type { IKibanaSearchRequest, IKibanaSearchResponse } from '@kbn/data-plugin/common';
|
||||
import { number } from 'io-ts';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { LATEST_VULNERABILITIES_INDEX_PATTERN } from '../../../../common/constants';
|
||||
import {
|
||||
getSafeVulnerabilitiesQueryFilter,
|
||||
LATEST_VULNERABILITIES_INDEX_PATTERN,
|
||||
} from '../../../../common/constants';
|
||||
import { useKibana } from '../../../common/hooks/use_kibana';
|
||||
import { showErrorToast } from '../../../common/utils/show_error_toast';
|
||||
import { FindingsBaseEsQuery } from '../../../common/types';
|
||||
|
@ -29,24 +32,7 @@ interface VulnerabilitiesQuery extends FindingsBaseEsQuery {
|
|||
|
||||
export const getFindingsQuery = ({ query, sort, pageIndex, pageSize }: VulnerabilitiesQuery) => ({
|
||||
index: LATEST_VULNERABILITIES_INDEX_PATTERN,
|
||||
query: {
|
||||
...query,
|
||||
bool: {
|
||||
...query?.bool,
|
||||
filter: [
|
||||
...(query?.bool?.filter || []),
|
||||
{ exists: { field: 'vulnerability.score.base' } },
|
||||
{ exists: { field: 'vulnerability.score.version' } },
|
||||
{ exists: { field: 'vulnerability.severity' } },
|
||||
{ exists: { field: 'resource.name' } },
|
||||
{ match_phrase: { 'vulnerability.enumeration': 'CVE' } },
|
||||
],
|
||||
must_not: [
|
||||
...(query?.bool?.must_not || []),
|
||||
{ match_phrase: { 'vulnerability.severity': 'UNKNOWN' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
query: getSafeVulnerabilitiesQueryFilter(query),
|
||||
from: pageIndex * pageSize,
|
||||
size: pageSize,
|
||||
sort,
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { VulnSeverity } from '../../../common/types';
|
||||
|
||||
export interface VulnerabilityRecord {
|
||||
'@timestamp': string;
|
||||
resource?: {
|
||||
|
@ -86,7 +88,7 @@ export interface Vulnerability {
|
|||
id: string;
|
||||
title: string;
|
||||
reference: string;
|
||||
severity: string;
|
||||
severity: VulnSeverity;
|
||||
cvss: {
|
||||
nvd: VectorScoreBase;
|
||||
redhat?: VectorScoreBase;
|
||||
|
|
|
@ -364,7 +364,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
|
|||
if (!vulnerabilityRow.vulnerability.severity) {
|
||||
return null;
|
||||
}
|
||||
return <SeverityStatusBadge status={vulnerabilityRow.vulnerability.severity} />;
|
||||
return <SeverityStatusBadge severity={vulnerabilityRow.vulnerability.severity} />;
|
||||
}
|
||||
|
||||
if (columnId === vulnerabilitiesColumns.package) {
|
||||
|
|
|
@ -166,7 +166,7 @@ export const VulnerabilityFindingFlyout = ({
|
|||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SeverityStatusBadge status={vulnerability?.severity} />
|
||||
<SeverityStatusBadge severity={vulnerability?.severity} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { EuiPageHeader } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiPageHeader, EuiSpacer } from '@elastic/eui';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states';
|
||||
import {
|
||||
VULNERABILITY_DASHBOARD_CONTAINER,
|
||||
VULNERABILITY_DASHBOARD_PAGE_HEADER,
|
||||
} from '../compliance_dashboard/test_subjects';
|
||||
import { VulnerabilityStatistics } from './vulnerability_statistics';
|
||||
import { CloudPosturePageTitle } from '../../components/cloud_posture_page_title';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
|
||||
|
@ -36,7 +37,10 @@ export const VulnerabilityDashboard = () => {
|
|||
{getSetupStatus?.data?.vuln_mgmt?.status !== 'indexed' ? (
|
||||
<NoVulnerabilitiesStates />
|
||||
) : (
|
||||
<div data-test-subj={VULNERABILITY_DASHBOARD_CONTAINER}>{/* temporarily empty */}</div>
|
||||
<div data-test-subj={VULNERABILITY_DASHBOARD_CONTAINER}>
|
||||
<EuiSpacer />
|
||||
<VulnerabilityStatistics />
|
||||
</div>
|
||||
)}
|
||||
</CloudPosturePage>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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, { useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHealth } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SEVERITY } from '../../../common/constants';
|
||||
import { useCnvmStatisticsApi } from '../../common/api/use_vulnerabilities_stats_api';
|
||||
import { useNavigateVulnerabilities } from '../../common/hooks/use_navigate_findings';
|
||||
import { CompactFormattedNumber } from '../../components/compact_formatted_number';
|
||||
import { getSeverityStatusColor } from '../../common/utils/get_vulnerability_colors';
|
||||
import { CspCounterCard } from '../../components/csp_counter_card';
|
||||
|
||||
export const VulnerabilityStatistics = () => {
|
||||
const navToVulnerabilities = useNavigateVulnerabilities();
|
||||
const getCnvmStats = useCnvmStatisticsApi();
|
||||
|
||||
const stats = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'cloud-regions-stat',
|
||||
title: <CompactFormattedNumber number={getCnvmStats.data?.cnvmStatistics.cloudRegions} />,
|
||||
description: i18n.translate('xpack.csp.cnvmDashboard.statistics.cloudRegionTitle', {
|
||||
defaultMessage: 'Cloud Regions',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'assets-scanned-stat',
|
||||
title: (
|
||||
<CompactFormattedNumber number={getCnvmStats.data?.cnvmStatistics.resourcesScanned} />
|
||||
),
|
||||
description: i18n.translate('xpack.csp.cnvmDashboard.statistics.resourcesScannedTitle', {
|
||||
defaultMessage: 'Resources Scanned',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'critical-count-stat',
|
||||
title: <CompactFormattedNumber number={getCnvmStats.data?.cnvmStatistics.criticalCount} />,
|
||||
description: (
|
||||
<EuiHealth color={getSeverityStatusColor(SEVERITY.CRITICAL)}>
|
||||
{i18n.translate('xpack.csp.cnvmDashboard.statistics.criticalTitle', {
|
||||
defaultMessage: 'Critical',
|
||||
})}
|
||||
</EuiHealth>
|
||||
),
|
||||
onClick: () => {
|
||||
navToVulnerabilities({ 'vulnerability.severity': SEVERITY.CRITICAL });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'high-count-stat',
|
||||
title: <CompactFormattedNumber number={getCnvmStats.data?.cnvmStatistics.highCount} />,
|
||||
description: (
|
||||
<EuiHealth color={getSeverityStatusColor(SEVERITY.HIGH)}>
|
||||
{i18n.translate('xpack.csp.cnvmDashboard.statistics.highTitle', {
|
||||
defaultMessage: 'High',
|
||||
})}
|
||||
</EuiHealth>
|
||||
),
|
||||
onClick: () => {
|
||||
navToVulnerabilities({ 'vulnerability.severity': SEVERITY.HIGH });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'medium-count-stat',
|
||||
title: <CompactFormattedNumber number={getCnvmStats.data?.cnvmStatistics.mediumCount} />,
|
||||
description: (
|
||||
<EuiHealth color={getSeverityStatusColor(SEVERITY.MEDIUM)}>
|
||||
{i18n.translate('xpack.csp.cnvmDashboard.statistics.mediumTitle', {
|
||||
defaultMessage: 'Medium',
|
||||
})}
|
||||
</EuiHealth>
|
||||
),
|
||||
onClick: () => {
|
||||
navToVulnerabilities({ 'vulnerability.severity': SEVERITY.MEDIUM });
|
||||
},
|
||||
},
|
||||
],
|
||||
[getCnvmStats.data, navToVulnerabilities]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
{stats.map((stat) => (
|
||||
<EuiFlexItem>
|
||||
<CspCounterCard {...stat} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -14,6 +14,7 @@ import type {
|
|||
} from '../types';
|
||||
import { PLUGIN_ID } from '../../common';
|
||||
import { defineGetComplianceDashboardRoute } from './compliance_dashboard/compliance_dashboard';
|
||||
import { defineGetVulnerabilitiesDashboardRoute } from './vulnerabilities_dashboard/vulnerabilities_dashboard';
|
||||
import { defineGetBenchmarksRoute } from './benchmarks/benchmarks';
|
||||
import { defineGetCspStatusRoute } from './status/status';
|
||||
import { defineFindCspRuleTemplateRoute } from './csp_rule_template/get_csp_rule_template';
|
||||
|
@ -33,6 +34,7 @@ export function setupRoutes({
|
|||
}) {
|
||||
const router = core.http.createRouter<CspRequestHandlerContext>();
|
||||
defineGetComplianceDashboardRoute(router);
|
||||
defineGetVulnerabilitiesDashboardRoute(router);
|
||||
defineGetBenchmarksRoute(router);
|
||||
defineGetCspStatusRoute(router);
|
||||
defineFindCspRuleTemplateRoute(router);
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 { QueryDslQueryContainer, SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { LATEST_VULNERABILITIES_INDEX_DEFAULT_NS } from '../../../common/constants';
|
||||
|
||||
export interface VulnerabilitiesStatisticsQueryResult {
|
||||
critical: {
|
||||
doc_count: number;
|
||||
};
|
||||
high: {
|
||||
doc_count: number;
|
||||
};
|
||||
medium: {
|
||||
doc_count: number;
|
||||
};
|
||||
resources_scanned: {
|
||||
value: number;
|
||||
};
|
||||
cloud_regions: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const getVulnerabilitiesStatisticsQuery = (
|
||||
query: QueryDslQueryContainer
|
||||
): SearchRequest => ({
|
||||
size: 0,
|
||||
query,
|
||||
index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS,
|
||||
aggs: {
|
||||
critical: {
|
||||
filter: { term: { 'vulnerability.severity': 'CRITICAL' } },
|
||||
},
|
||||
high: {
|
||||
filter: { term: { 'vulnerability.severity': 'HIGH' } },
|
||||
},
|
||||
medium: {
|
||||
filter: { term: { 'vulnerability.severity': 'MEDIUM' } },
|
||||
},
|
||||
resources_scanned: {
|
||||
cardinality: {
|
||||
field: 'resource.id',
|
||||
},
|
||||
},
|
||||
cloud_regions: {
|
||||
cardinality: {
|
||||
field: 'cloud.region',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const getVulnerabilitiesStatistics = async (
|
||||
esClient: ElasticsearchClient,
|
||||
query: QueryDslQueryContainer
|
||||
) => {
|
||||
const queryResult = await esClient.search<unknown, VulnerabilitiesStatisticsQueryResult>(
|
||||
getVulnerabilitiesStatisticsQuery(query)
|
||||
);
|
||||
|
||||
return {
|
||||
criticalCount: queryResult.aggregations?.critical.doc_count,
|
||||
highCount: queryResult.aggregations?.high.doc_count,
|
||||
mediumCount: queryResult.aggregations?.medium.doc_count,
|
||||
resourcesScanned: queryResult.aggregations?.resources_scanned.value,
|
||||
cloudRegions: queryResult.aggregations?.cloud_regions.value,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { CnvmDashboardData } from '../../../common/types';
|
||||
import {
|
||||
VULNERABILITIES_DASHBOARD_ROUTE_PATH,
|
||||
getSafeVulnerabilitiesQueryFilter,
|
||||
} from '../../../common/constants';
|
||||
import { CspRouter } from '../../types';
|
||||
import { getVulnerabilitiesStatistics } from './get_vulnerabilities_statistics';
|
||||
|
||||
export interface KeyDocCount<TKey = string> {
|
||||
key: TKey;
|
||||
doc_count: number;
|
||||
}
|
||||
|
||||
export const defineGetVulnerabilitiesDashboardRoute = (router: CspRouter): void =>
|
||||
router.get(
|
||||
{
|
||||
path: VULNERABILITIES_DASHBOARD_ROUTE_PATH,
|
||||
validate: false,
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const cspContext = await context.csp;
|
||||
|
||||
try {
|
||||
const esClient = cspContext.esClient.asCurrentUser;
|
||||
|
||||
const query = getSafeVulnerabilitiesQueryFilter();
|
||||
|
||||
const [cnvmStatistics] = await Promise.all([getVulnerabilitiesStatistics(esClient, query)]);
|
||||
|
||||
const body: CnvmDashboardData = {
|
||||
cnvmStatistics,
|
||||
};
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
cspContext.logger.error(`Error while fetching Vulnerabilities stats: ${err}`);
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue