diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts index 0dc376b19f179..cbeb334e2aa61 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts @@ -190,18 +190,37 @@ export const getBenchmarkCisName = (benchmarkId: BenchmarksCisId) => { } }; +const CLOUD_PROVIDER_NAMES = { + AWS: 'Amazon Web Services', + AZURE: 'Microsoft Azure', + GCP: 'Google Cloud Platform', +}; + export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => { switch (benchmarkId) { case 'cis_k8s': return 'Kubernetes'; case 'cis_azure': - return 'Microsoft Azure'; + return CLOUD_PROVIDER_NAMES.AZURE; case 'cis_aws': - return 'Amazon Web Services'; + return CLOUD_PROVIDER_NAMES.AWS; case 'cis_eks': return 'Amazon Elastic Kubernetes Service'; case 'cis_gcp': - return 'Google Cloud Provider'; + return CLOUD_PROVIDER_NAMES.GCP; + } +}; + +export const getCloudProviderNameFromAbbreviation = (cloudProvider: string) => { + switch (cloudProvider) { + case 'azure': + return CLOUD_PROVIDER_NAMES.AZURE; + case 'aws': + return CLOUD_PROVIDER_NAMES.AWS; + case 'gcp': + return CLOUD_PROVIDER_NAMES.GCP; + default: + return cloudProvider; } }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_provider_icon.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_provider_icon.tsx new file mode 100644 index 0000000000000..b6acdac0ee1b1 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_provider_icon.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIcon, EuiToolTip, IconSize } from '@elastic/eui'; +import { CSSInterpolation } from '@emotion/serialize'; +import { getCloudProviderNameFromAbbreviation } from '../../common/utils/helpers'; +import googleCloudLogo from '../assets/icons/google_cloud_logo.svg'; + +interface Props { + cloudProvider: string; + style?: CSSInterpolation; + size?: IconSize; +} + +const getCloudProviderIcon = (cloudProvider: string) => { + switch (cloudProvider) { + case 'azure': + return 'logoAzure'; + case 'aws': + return 'logoAWS'; + case 'gcp': + return googleCloudLogo; + default: + return undefined; + } +}; + +export const CloudProviderIcon = ({ cloudProvider, size, style }: Props) => { + const iconType = getCloudProviderIcon(cloudProvider); + + if (!iconType) { + return null; + } + + const name = getCloudProviderNameFromAbbreviation(cloudProvider); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_by_resource/severity_map.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx similarity index 89% rename from x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_by_resource/severity_map.tsx rename to x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx index 78e5513271441..b22846b624b9c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_by_resource/severity_map.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx @@ -16,9 +16,9 @@ import { } from '@elastic/eui'; import { PaletteColorStop } from '@elastic/eui/src/components/color_picker/color_palette_picker'; import { i18n } from '@kbn/i18n'; -import { getSeverityStatusColor } from '../../../common/utils/get_vulnerability_colors'; -import { VulnSeverity } from '../../../../common/types_old'; -import { SeverityStatusBadge } from '../../../components/vulnerability_badges'; +import { getSeverityStatusColor } from '../common/utils/get_vulnerability_colors'; +import { VulnSeverity } from '../../common/types_old'; +import { SeverityStatusBadge } from './vulnerability_badges'; interface Props { total: number; @@ -50,7 +50,7 @@ const formatPercentage = (percentage: number) => { return `${percentage.toFixed(1)}%`; }; -export const SeverityMap = ({ severityMap, total }: Props) => { +export const VulnerabilitySeverityMap = ({ severityMap, total }: Props) => { const { euiTheme } = useEuiTheme(); const severityMapPallet: PaletteColorStop[] = []; @@ -87,10 +87,7 @@ export const SeverityMap = ({ severityMap, total }: Props) => { width: 256px; `} anchorClassName={css` - height: ${euiTheme.size.xl}; - flex-grow: 1; - display: flex; - align-items: center; + margin-left: ${euiTheme.size.xs}; `} position="left" title={i18n.translate('xpack.csp.vulnerabilitiesByResource.severityMap.tooltipTitle', { @@ -102,7 +99,8 @@ export const SeverityMap = ({ severityMap, total }: Props) => { type="fixed" palette={severityMapPallet} className={css` - width: 100%; + width: 80px; + height: 6px; `} /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/constants.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/constants.ts index 52361bb9b2c6b..9ad49296dd0dc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/constants.ts @@ -19,10 +19,15 @@ export const VULNERABILITY_FIELDS = { PACKAGE_NAME: 'package.name', PACKAGE_VERSION: 'package.version', PACKAGE_FIXED_VERSION: 'package.fixed_version', + CLOUD_ACCOUNT_NAME: 'cloud.account.name', + CLOUD_PROVIDER: 'cloud.provider', + DESCRIPTION: 'vulnerability.description', } as const; export const GROUPING_OPTIONS = { RESOURCE_NAME: VULNERABILITY_FIELDS.RESOURCE_NAME, + CLOUD_ACCOUNT_NAME: VULNERABILITY_FIELDS.CLOUD_ACCOUNT_NAME, + CVE: VULNERABILITY_FIELDS.VULNERABILITY_ID, }; export const defaultGroupingOptions: GroupOption[] = [ @@ -30,6 +35,14 @@ export const defaultGroupingOptions: GroupOption[] = [ label: GROUPING_LABELS.RESOURCE_NAME, key: GROUPING_OPTIONS.RESOURCE_NAME, }, + { + label: GROUPING_LABELS.CLOUD_ACCOUNT_NAME, + key: GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME, + }, + { + label: 'CVE', + key: GROUPING_OPTIONS.CVE, + }, ]; export const getDefaultQuery = ({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_grouped_vulnerabilities.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_grouped_vulnerabilities.tsx index fda12c4b06d41..371ab2fa038c0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_grouped_vulnerabilities.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_grouped_vulnerabilities.tsx @@ -34,6 +34,21 @@ export interface VulnerabilitiesGroupingAggregation { buckets?: GenericBuckets[]; }; isLoading?: boolean; + critical?: { + doc_count?: NumberOrNull; + }; + high?: { + doc_count?: NumberOrNull; + }; + medium?: { + doc_count?: NumberOrNull; + }; + low?: { + doc_count?: NumberOrNull; + }; + cloudProvider?: { + buckets?: GenericBuckets[]; + }; } export type VulnerabilitiesRootGroupingAggregation = diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_by_resource.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_by_resource.ts deleted file mode 100644 index 3aa152d427143..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_by_resource.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 } from '@tanstack/react-query'; -import { lastValueFrom } from 'rxjs'; -import type { IKibanaSearchRequest, IKibanaSearchResponse } from '@kbn/data-plugin/common'; -import { - SearchRequest, - SearchResponse, - AggregationsCardinalityAggregate, - AggregationsMultiBucketAggregateBase, - AggregationsSingleBucketAggregateBase, - AggregationsStringRareTermsBucketKeys, - AggregationsStringTermsBucketKeys, - SortOrder, -} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { - LATEST_VULNERABILITIES_INDEX_PATTERN, - VULNERABILITIES_SEVERITY, -} from '../../../../common/constants'; - -import { MAX_FINDINGS_TO_LOAD } from '../../../common/constants'; -import { useKibana } from '../../../common/hooks/use_kibana'; -import { showErrorToast } from '../../../common/utils/show_error_toast'; -import { FindingsBaseEsQuery } from '../../../common/types'; - -type LatestFindingsRequest = IKibanaSearchRequest; -type LatestFindingsResponse = IKibanaSearchResponse>; - -interface VulnerabilitiesAggs { - count: AggregationsMultiBucketAggregateBase; - total: AggregationsCardinalityAggregate; - resources: AggregationsMultiBucketAggregateBase; -} - -interface FindingsAggBucket extends AggregationsStringRareTermsBucketKeys { - name: AggregationsMultiBucketAggregateBase; - region: AggregationsMultiBucketAggregateBase; - critical: AggregationsSingleBucketAggregateBase; - high: AggregationsSingleBucketAggregateBase; - medium: AggregationsSingleBucketAggregateBase; - low: AggregationsSingleBucketAggregateBase; -} - -interface VulnerabilitiesQuery extends FindingsBaseEsQuery { - sortOrder: SortOrder; - enabled: boolean; - pageIndex: number; - pageSize: number; -} - -export const getQuery = ({ - query, - sortOrder = 'desc', - pageIndex, - pageSize, -}: VulnerabilitiesQuery) => ({ - index: LATEST_VULNERABILITIES_INDEX_PATTERN, - query, - aggs: { - total: { cardinality: { field: 'resource.id' } }, - resources: { - terms: { - field: 'resource.id', - size: MAX_FINDINGS_TO_LOAD * 3, - // in case there are more resources then size, ensuring resources with more vulnerabilities - // will be included first, and then vulnerabilities with critical and high severity - order: [{ _count: sortOrder }, { critical: 'desc' }, { high: 'desc' }, { medium: 'desc' }], - }, - aggs: { - vulnerabilitiesCountBucketSort: { - bucket_sort: { - sort: [{ _count: { order: sortOrder } }], - from: pageIndex * pageSize, - size: pageSize, - }, - }, - name: { - terms: { field: 'resource.name', size: 1 }, - }, - region: { - terms: { field: 'cloud.region', size: 1 }, - }, - critical: { - filter: { - term: { - 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.CRITICAL }, - }, - }, - }, - high: { - filter: { - term: { - 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.HIGH }, - }, - }, - }, - medium: { - filter: { - term: { - 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.MEDIUM }, - }, - }, - }, - low: { - filter: { - term: { 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.LOW } }, - }, - }, - }, - }, - }, - size: 0, -}); -const getFirstKey = ( - buckets: AggregationsMultiBucketAggregateBase['buckets'] -) => { - return !!Array.isArray(buckets) && !!buckets.length ? (buckets[0].key as string) : ''; -}; -const createVulnerabilitiesByResource = (resource: FindingsAggBucket) => ({ - resource: { - id: resource.key, - name: getFirstKey(resource.name.buckets), - }, - cloud: { - region: getFirstKey(resource.region.buckets), - }, - vulnerabilities_count: resource.doc_count, - severity_map: { - critical: resource.critical.doc_count, - high: resource.high.doc_count, - medium: resource.medium.doc_count, - low: resource.low.doc_count, - }, -}); - -export const useLatestVulnerabilitiesByResource = (options: VulnerabilitiesQuery) => { - const { - data, - notifications: { toasts }, - } = useKibana().services; - return useQuery( - [LATEST_VULNERABILITIES_INDEX_PATTERN, 'resource', options], - async () => { - const { - rawResponse: { hits, aggregations }, - } = await lastValueFrom( - data.search.search({ - params: getQuery(options), - }) - ); - - if (!aggregations) throw new Error('Failed to aggregate by resource'); - - if (!Array.isArray(aggregations.resources.buckets)) - throw new Error('Failed to group by, missing resource id'); - - return { - page: aggregations.resources.buckets.map(createVulnerabilitiesByResource), - total: aggregations.total.value, - total_vulnerabilities: hits.total as number, - }; - }, - { - staleTime: 5000, - keepPreviousData: true, - enabled: options.enabled, - onError: (err: Error) => showErrorToast(toasts, err), - } - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx index 45fdc5c71a342..51d314f570a34 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx @@ -16,7 +16,10 @@ import { import { useMemo } from 'react'; import { LOCAL_STORAGE_VULNERABILITIES_GROUPING_KEY } from '../../../common/constants'; import { useDataViewContext } from '../../../common/contexts/data_view_context'; -import { LATEST_VULNERABILITIES_RETENTION_POLICY } from '../../../../common/constants'; +import { + LATEST_VULNERABILITIES_RETENTION_POLICY, + VULNERABILITIES_SEVERITY, +} from '../../../../common/constants'; import { VulnerabilitiesGroupingAggregation, VulnerabilitiesRootGroupingAggregation, @@ -48,12 +51,45 @@ const getAggregationsByGroupField = (field: string): NamedAggregation[] => { field, }, }, + critical: { + filter: { + term: { + 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.CRITICAL }, + }, + }, + }, + high: { + filter: { + term: { + 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.HIGH }, + }, + }, + }, + medium: { + filter: { + term: { + 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.MEDIUM }, + }, + }, + }, + low: { + filter: { + term: { 'vulnerability.severity': { value: VULNERABILITIES_SEVERITY.LOW } }, + }, + }, }, ]; switch (field) { case GROUPING_OPTIONS.RESOURCE_NAME: return [...aggMetrics, getTermAggregation('resourceId', VULNERABILITY_FIELDS.RESOURCE_ID)]; + case GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return [ + ...aggMetrics, + getTermAggregation('cloudProvider', VULNERABILITY_FIELDS.CLOUD_PROVIDER), + ]; + case GROUPING_OPTIONS.CVE: + return [...aggMetrics, getTermAggregation('description', VULNERABILITY_FIELDS.DESCRIPTION)]; } return aggMetrics; }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx index 82626fd684513..7de747b458b06 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx @@ -16,12 +16,20 @@ import { import { css } from '@emotion/react'; import { GroupPanelRenderer, RawBucket, StatRenderer } from '@kbn/securitysolution-grouping/src'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { getCloudProviderNameFromAbbreviation } from '../../../common/utils/helpers'; import { VulnerabilitiesGroupingAggregation } from './hooks/use_grouped_vulnerabilities'; import { GROUPING_OPTIONS } from './constants'; import { VULNERABILITIES_GROUPING_COUNTER } from './test_subjects'; import { NULL_GROUPING_MESSAGES, NULL_GROUPING_UNIT, VULNERABILITIES } from './translations'; import { getAbbreviatedNumber } from '../../common/utils/get_abbreviated_number'; -import { LoadingGroup, NullGroup } from '../../components/cloud_security_grouping'; +import { + firstNonNullValue, + LoadingGroup, + NullGroup, +} from '../../components/cloud_security_grouping'; +import { VulnerabilitySeverityMap } from '../../components/vulnerability_severity_map'; +import { CloudProviderIcon } from '../../components/cloud_provider_icon'; export const groupPanelRenderer: GroupPanelRenderer = ( selectedGroup, @@ -37,6 +45,12 @@ export const groupPanelRenderer: GroupPanelRenderer ); + const cloudProvider = firstNonNullValue(bucket.cloudProvider?.buckets?.[0]?.key); + const description = firstNonNullValue(bucket.description?.buckets?.[0]?.key); + const cloudProviderName = cloudProvider + ? getCloudProviderNameFromAbbreviation(cloudProvider) + : ''; + switch (selectedGroup) { case GROUPING_OPTIONS.RESOURCE_NAME: return nullGroupMessage ? ( @@ -66,6 +80,32 @@ export const groupPanelRenderer: GroupPanelRenderer ); + case GROUPING_OPTIONS.CLOUD_ACCOUNT_NAME: + return nullGroupMessage ? ( + renderNullGroup(NULL_GROUPING_MESSAGES.CLOUD_ACCOUNT_NAME) + ) : ( + + {cloudProvider && ( + + + + )} + + + + + {bucket.key_as_string} + + + + + {cloudProviderName} + + + + + + ); default: return nullGroupMessage ? ( renderNullGroup(NULL_GROUPING_MESSAGES.DEFAULT) @@ -78,6 +118,20 @@ export const groupPanelRenderer: GroupPanelRenderer{bucket.key_as_string} + {description && ( + + + + {description} + + + + )} @@ -109,6 +163,35 @@ const VulnerabilitiesCountComponent = ({ const VulnerabilitiesCount = React.memo(VulnerabilitiesCountComponent); +const SeverityStatsComponent = ({ + bucket, +}: { + bucket: RawBucket; +}) => { + const severityMap = { + critical: bucket.critical?.doc_count ?? 0, + high: bucket.high?.doc_count ?? 0, + medium: bucket.medium?.doc_count ?? 0, + low: bucket.low?.doc_count ?? 0, + }; + + return ( + + + + + + + + + ); +}; + +const SeverityStats = React.memo(SeverityStatsComponent); + export const groupStatsRenderer = ( selectedGroup: string, bucket: RawBucket @@ -118,6 +201,10 @@ export const groupStatsRenderer = ( title: VULNERABILITIES, renderer: , }, + { + title: '', + renderer: , + }, ]; return defaultBadges; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts index 65ca61056f612..f7d88f396f5c6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts @@ -40,17 +40,26 @@ export const NULL_GROUPING_MESSAGES = { RESOURCE_NAME: i18n.translate('xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle', { defaultMessage: 'No resource', }), + CLOUD_ACCOUNT_NAME: i18n.translate( + 'xpack.csp.vulnerabilities.grouping.cloudAccount.nullGroupTitle', + { + defaultMessage: 'No cloud account', + } + ), DEFAULT: i18n.translate('xpack.csp.vulnerabilities.grouping.default.nullGroupTitle', { defaultMessage: 'No grouping', }), }; export const GROUPING_LABELS = { - RESOURCE_NAME: i18n.translate('xpack.csp.findings.latestFindings.groupByResource', { + RESOURCE_NAME: i18n.translate('xpack.csp.vulnerabilities.groupBy.resource', { defaultMessage: 'Resource', }), + CLOUD_ACCOUNT_NAME: i18n.translate('xpack.csp.vulnerabilities.groupBy.cloudAccount', { + defaultMessage: 'Cloud account', + }), }; -export const groupingTitle = i18n.translate('xpack.csp.vulnerabilities.latestFindings.groupBy', { +export const groupingTitle = i18n.translate('xpack.csp.vulnerabilities.groupBy', { defaultMessage: 'Group vulnerabilities by', }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/vulnerabilities_pathname_handler.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/vulnerabilities_pathname_handler.ts deleted file mode 100644 index 0d9e0b660f1f8..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/vulnerabilities_pathname_handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { EuiComboBoxOptionOption } from '@elastic/eui'; -import { findingsNavigation } from '../../../common/navigation/constants'; -import { FindingsGroupByKind } from '../../../common/types'; - -export const vulnerabilitiesPathnameHandler = ( - opts: Array> -) => { - const [firstOption] = opts; - - switch (firstOption?.value) { - case 'resource': - return findingsNavigation.vulnerabilities_by_resource.path; - case 'default': - default: - return findingsNavigation.vulnerabilities.path; - } -}; diff --git a/x-pack/test/cloud_security_posture_functional/mocks/vulnerabilities_latest_mock.ts b/x-pack/test/cloud_security_posture_functional/mocks/vulnerabilities_latest_mock.ts index f0ffd4aebb388..301c328993c8b 100644 --- a/x-pack/test/cloud_security_posture_functional/mocks/vulnerabilities_latest_mock.ts +++ b/x-pack/test/cloud_security_posture_functional/mocks/vulnerabilities_latest_mock.ts @@ -182,10 +182,10 @@ export const vulnerabilitiesLatestMock = [ }, }, cloud: { - provider: 'aws', + provider: 'gcp', region: 'eu-west-1', account: { - name: 'elastic-security-cloud-security-dev', + name: 'elastic-security-cloud-security-gcp', id: '704479110758', }, }, diff --git a/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities_grouping.ts b/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities_grouping.ts index 8e569d27b8a4d..db0702ab289e8 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities_grouping.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities_grouping.ts @@ -19,6 +19,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const resourceName1 = 'name-ng-1-Node'; const resourceName2 = 'othername-june12-8-8-0-1'; + const cloudAccountName1 = 'elastic-security-cloud-security-dev'; + const cloudAccountName2 = 'elastic-security-cloud-security-gcp'; + + const cloudProviderName1 = 'Amazon Web Services'; + const cloudProviderName2 = 'Google Cloud Platform'; + describe('Vulnerabilities Page - Grouping', function () { this.tags(['cloud_security_posture_findings_grouping']); let findings: typeof pageObjects.findings; @@ -45,11 +51,88 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('Default Grouping', async () => { - it('groups vulnerabilities by resource and sort by compliance score desc', async () => { - const groupSelector = await findings.groupSelector(); + it('groups vulnerabilities by cloud account and sort by number of vulnerabilities desc', async () => { + const groupSelector = findings.groupSelector(); await groupSelector.openDropDown(); - await groupSelector.setValue('Resource'); + await groupSelector.setValue('Cloud account'); + + const grouping = await findings.findingsGrouping(); + + const order = [ + { + cloudAccountName: cloudAccountName1, + cloudProviderName: cloudProviderName1, + findingsCount: '1', + }, + { + cloudAccountName: cloudAccountName2, + cloudProviderName: cloudProviderName2, + findingsCount: '1', + }, + ]; + + await asyncForEach( + order, + async ({ cloudAccountName, cloudProviderName, findingsCount }, index) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(cloudAccountName); + expect(await groupRow.getVisibleText()).to.contain(cloudProviderName); + + expect( + await ( + await groupRow.findByTestSubject('vulnerabilities_grouping_counter') + ).getVisibleText() + ).to.be(findingsCount); + } + ); + + const groupCount = await grouping.getGroupCount(); + expect(groupCount).to.be('2 groups'); + + const unitCount = await grouping.getUnitCount(); + expect(unitCount).to.be('2 vulnerabilities'); + }); + it('groups vulnerabilities by CVE and sort by number of vulnerabilities desc', async () => { + const groupSelector = findings.groupSelector(); + await groupSelector.openDropDown(); + await groupSelector.setValue('CVE'); + + const grouping = await findings.findingsGrouping(); + + const order = [ + { + name: vulnerabilitiesLatestMock[0].vulnerability.id, + description: vulnerabilitiesLatestMock[0].vulnerability.description, + findingsCount: '1', + }, + { + name: vulnerabilitiesLatestMock[1].vulnerability.id, + description: vulnerabilitiesLatestMock[1].vulnerability.description, + findingsCount: '1', + }, + ]; + + await asyncForEach(order, async ({ name, description, findingsCount }, index) => { + const groupRow = await grouping.getRowAtIndex(index); + expect(await groupRow.getVisibleText()).to.contain(name); + expect(await groupRow.getVisibleText()).to.contain(description); + expect( + await ( + await groupRow.findByTestSubject('vulnerabilities_grouping_counter') + ).getVisibleText() + ).to.be(findingsCount); + }); + + const groupCount = await grouping.getGroupCount(); + expect(groupCount).to.be('2 groups'); + + const unitCount = await grouping.getUnitCount(); + expect(unitCount).to.be('2 vulnerabilities'); + }); + it('groups vulnerabilities by resource and sort by number of vulnerabilities desc', async () => { + const groupSelector = findings.groupSelector(); + await groupSelector.setValue('Resource'); const grouping = await findings.findingsGrouping(); const resourceOrder = [