diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index dea87bf81826f..f75289bfa020b 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -16,10 +16,10 @@ export interface FindingsEvaluation { totalFindings: number; totalPassed: number; totalFailed: number; + postureScore: Score; } export interface Stats extends FindingsEvaluation { - postureScore: Score; resourcesEvaluated?: number; } diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 597e037cb68d8..6356f00b5ec9f 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -5,13 +5,15 @@ * 2.0. */ -import { euiPaletteForStatus } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-theme'; import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA } from '../../common/constants'; -const [success, warning, danger] = euiPaletteForStatus(3); +export const statusColors = { + passed: euiThemeVars.euiColorVis0, + failed: euiThemeVars.euiColorVis9, +}; -export const statusColors = { success, warning, danger }; export const CSP_MOMENT_FORMAT = 'MMMM D, YYYY @ HH:mm:ss.SSS'; export type CloudPostureIntegrations = typeof cloudPostureIntegrations; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/cloud_posture_score_chart.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/cloud_posture_score_chart.tsx index 20ebd4b893f20..b0f16e0066733 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/cloud_posture_score_chart.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/cloud_posture_score_chart.tsx @@ -29,6 +29,7 @@ import { import { FormattedDate, FormattedTime } from '@kbn/i18n-react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { statusColors } from '../../../common/constants'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; import { CompactFormattedNumber } from '../../../components/compact_formatted_number'; import type { Evaluation, PostureTrend, Stats } from '../../../../common/types'; @@ -163,7 +164,7 @@ export const CloudPostureScoreChart = ({ onEvalCounterClick(RULE_PASSED)} tooltipContent={i18n.translate( 'xpack.csp.cloudPostureScoreChart.counterLink.passedFindingsTooltip', @@ -174,7 +175,7 @@ export const CloudPostureScoreChart = ({ onEvalCounterClick(RULE_FAILED)} tooltipContent={i18n.translate( 'xpack.csp.cloudPostureScoreChart.counterLink.failedFindingsTooltip', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.test.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.test.ts index 6b2c00c507e6f..48f8c25b68703 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.test.ts @@ -12,6 +12,7 @@ const podsAgg = { totalFindings: 2, totalPassed: 1, totalFailed: 1, + postureScore: 50.0, }; const etcdAgg = { @@ -19,6 +20,7 @@ const etcdAgg = { totalFindings: 5, totalPassed: 0, totalFailed: 5, + postureScore: 0, }; const clusterAgg = { @@ -26,6 +28,7 @@ const clusterAgg = { totalFindings: 2, totalPassed: 2, totalFailed: 0, + postureScore: 100.0, }; const systemAgg = { @@ -33,6 +36,7 @@ const systemAgg = { totalFindings: 10, totalPassed: 6, totalFailed: 4, + postureScore: 60.0, }; const apiAgg = { @@ -40,6 +44,7 @@ const apiAgg = { totalFindings: 19100, totalPassed: 2100, totalFailed: 17000, + postureScore: 10.9, }; const serverAgg = { @@ -47,6 +52,7 @@ const serverAgg = { totalFindings: 7, totalPassed: 4, totalFailed: 3, + postureScore: 57.1, }; const mockData: RisksTableProps['data'] = [ @@ -59,15 +65,11 @@ const mockData: RisksTableProps['data'] = [ ]; describe('getTopRisks', () => { - it('returns sorted by failed findings', () => { - expect(getTopRisks([systemAgg, etcdAgg, apiAgg], 3)).toEqual([apiAgg, etcdAgg, systemAgg]); + it('returns sorted by posture score', () => { + expect(getTopRisks([systemAgg, etcdAgg, apiAgg], 3)).toEqual([etcdAgg, apiAgg, systemAgg]); }); - it('return array filtered with failed findings only', () => { - expect(getTopRisks([systemAgg, clusterAgg, apiAgg], 3)).toEqual([apiAgg, systemAgg]); - }); - - it('return sorted and filtered array with the correct number of elements', () => { - expect(getTopRisks(mockData, 5)).toEqual([apiAgg, etcdAgg, systemAgg, serverAgg, podsAgg]); + it('return sorted array with the correct number of elements', () => { + expect(getTopRisks(mockData, 5)).toEqual([etcdAgg, apiAgg, podsAgg, serverAgg, systemAgg]); }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx index a1850a793ab3e..34fdff3eac69c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx @@ -7,24 +7,26 @@ import React, { useMemo } from 'react'; import { - EuiBasicTable, EuiBasicTableColumn, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiInMemoryTable, EuiLink, EuiText, + EuiToolTip, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { statusColors } from '../../../common/constants'; import { ComplianceDashboardData, GroupedFindingsEvaluation } from '../../../../common/types'; -import { CompactFormattedNumber } from '../../../components/compact_formatted_number'; export interface RisksTableProps { data: ComplianceDashboardData['groupedFindingsEvaluation']; maxItems: number; onCellClick: (name: string) => void; onViewAllClick: () => void; + viewAllButtonTitle: string; compact?: boolean; } @@ -32,19 +34,23 @@ export const getTopRisks = ( groupedFindingsEvaluation: ComplianceDashboardData['groupedFindingsEvaluation'], maxItems: number ) => { - const filtered = groupedFindingsEvaluation.filter((x) => x.totalFailed > 0); - const sorted = filtered.slice().sort((first, second) => second.totalFailed - first.totalFailed); + const sorted = groupedFindingsEvaluation + .slice() + .sort((first, second) => first.postureScore - second.postureScore); return sorted.slice(0, maxItems); }; export const RisksTable = ({ - data: resourcesTypes, + data: cisSectionsEvaluations, maxItems, onCellClick, onViewAllClick, + viewAllButtonTitle, compact, }: RisksTableProps) => { + const { euiTheme } = useEuiTheme(); + const columns: Array> = useMemo( () => [ { @@ -62,49 +68,87 @@ export const RisksTable = ({ ), }, { - field: 'totalFailed', + field: 'postureScore', + width: '115px', name: compact ? '' - : i18n.translate('xpack.csp.dashboard.risksTable.findingsColumnLabel', { - defaultMessage: 'Findings', + : i18n.translate('xpack.csp.dashboard.risksTable.complianceColumnLabel', { + defaultMessage: 'Compliance', }), - render: ( - totalFailed: GroupedFindingsEvaluation['totalFailed'], - resource: GroupedFindingsEvaluation - ) => ( - <> - - - - - {'/'} - - - + render: (postureScore: GroupedFindingsEvaluation['postureScore'], data) => ( + + + + + + + + + + + {`${ + postureScore?.toFixed(0) || 0 + }%`} + + ), }, ], - [compact, onCellClick] + [ + compact, + euiTheme.border.radius.medium, + euiTheme.font.weight.bold, + euiTheme.size.s, + euiTheme.size.xs, + onCellClick, + ] ); - const items = useMemo(() => getTopRisks(resourcesTypes, maxItems), [resourcesTypes, maxItems]); + const sortedByComplianceScore = getTopRisks(cisSectionsEvaluations, maxItems); return ( - - rowHeader="name" - items={items} + + items={sortedByComplianceScore} columns={columns} />
- + {viewAllButtonTitle}
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx index d68e8d7364a3f..c658eac5462a5 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx @@ -43,42 +43,49 @@ export const mockDashboardData: ComplianceDashboardData = { totalFindings: 104, totalFailed: 0, totalPassed: 104, + postureScore: 100, }, { name: 'API Server', totalFindings: 27, totalFailed: 11, totalPassed: 16, + postureScore: 59.2, }, { name: 'Master Node Configuration Files', totalFindings: 17, totalFailed: 1, totalPassed: 16, + postureScore: 94.1, }, { name: 'Kubelet', totalFindings: 11, totalFailed: 4, totalPassed: 7, + postureScore: 63.6, }, { name: 'etcd', totalFindings: 6, totalFailed: 0, totalPassed: 6, + postureScore: 100, }, { name: 'Worker Node Configuration Files', totalFindings: 5, totalFailed: 0, totalPassed: 5, + postureScore: 100, }, { name: 'Scheduler', totalFindings: 2, totalFailed: 1, totalPassed: 1, + postureScore: 50.0, }, ], clusters: [ @@ -101,42 +108,49 @@ export const mockDashboardData: ComplianceDashboardData = { totalFindings: 104, totalFailed: 0, totalPassed: 104, + postureScore: 100, }, { name: 'API Server', totalFindings: 27, totalFailed: 11, totalPassed: 16, + postureScore: 59.2, }, { name: 'Master Node Configuration Files', totalFindings: 17, totalFailed: 1, totalPassed: 16, + postureScore: 94.1, }, { name: 'Kubelet', totalFindings: 11, totalFailed: 4, totalPassed: 7, + postureScore: 63.6, }, { name: 'etcd', totalFindings: 6, totalFailed: 0, totalPassed: 6, + postureScore: 100, }, { name: 'Worker Node Configuration Files', totalFindings: 5, totalFailed: 0, totalPassed: 5, + postureScore: 100, }, { name: 'Scheduler', totalFindings: 2, totalFailed: 1, totalPassed: 1, + postureScore: 50.0, }, ], trend: [ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_benchmarks_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_benchmarks_section.tsx index 26bce94cd49a3..288a41d0a7a96 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_benchmarks_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_benchmarks_section.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiFlexItem, EuiFlexGroup, useEuiTheme, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import { CloudPostureScoreChart } from '../compliance_charts/cloud_posture_score_chart'; import type { ComplianceDashboardData, Evaluation } from '../../../../common/types'; import { RisksTable } from '../compliance_charts/risks_table'; @@ -126,6 +127,10 @@ export const CloudBenchmarksSection = ({ onCellClick={(resourceTypeName) => handleCellClick(cluster.meta.clusterId, resourceTypeName) } + viewAllButtonTitle={i18n.translate( + 'xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle', + { defaultMessage: 'View all failed findings for this cluster' } + )} onViewAllClick={() => handleViewAllClick(cluster.meta.clusterId)} /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_summary_section.tsx index 005d174079f02..4168beb2ffd30 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cloud_summary_section.tsx @@ -9,6 +9,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FlexItemGrowSize } from '@elastic/eui/src/components/flex/flex_item'; +import { statusColors } from '../../../common/constants'; import { DASHBOARD_COUNTER_CARDS } from '../test_subjects'; import { CspCounterCard, CspCounterCardProps } from '../../../components/csp_counter_card'; import { CompactFormattedNumber } from '../../../components/compact_formatted_number'; @@ -79,7 +80,7 @@ export const CloudSummarySection = ({ { defaultMessage: 'Failing Findings' } ), title: , - titleColor: complianceData.stats.totalFailed > 0 ? 'danger' : 'text', + titleColor: complianceData.stats.totalFailed > 0 ? statusColors.failed : 'text', onClick: () => { navToFindings({ 'result.evaluation': RULE_FAILED }); }, @@ -131,6 +132,10 @@ export const CloudSummarySection = ({ maxItems={5} onCellClick={handleCellClick} onViewAllClick={handleViewAllClick} + viewAllButtonTitle={i18n.translate( + 'xpack.csp.dashboard.risksTable.viewAllButtonTitle', + { defaultMessage: 'View all failed findings' } + )} /> diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts index 2b288d6e696e8..ba97be21ad718 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts @@ -47,6 +47,9 @@ const mockClusterBuckets: ClusterBucket[] = [ passed_findings: { doc_count: 3, }, + score: { + value: 0.5, + }, }, { key: 'boo_type', @@ -57,6 +60,9 @@ const mockClusterBuckets: ClusterBucket[] = [ passed_findings: { doc_count: 3, }, + score: { + value: 0.5, + }, }, ], }, @@ -87,12 +93,14 @@ describe('getClustersFromAggs', () => { totalFindings: 6, totalFailed: 3, totalPassed: 3, + postureScore: 50.0, }, { name: 'boo_type', totalFindings: 6, totalFailed: 3, totalPassed: 3, + postureScore: 50.0, }, ], }, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts index 46c73c4250157..6af6d97f51e26 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts @@ -17,6 +17,9 @@ const resourceTypeBuckets: FailedFindingsBucket[] = [ passed_findings: { doc_count: 11, }, + score: { + value: 0.268, + }, }, { key: 'boo_type', @@ -27,6 +30,9 @@ const resourceTypeBuckets: FailedFindingsBucket[] = [ passed_findings: { doc_count: 6, }, + score: { + value: 0.545, + }, }, ]; @@ -39,12 +45,14 @@ describe('getFailedFindingsFromAggs', () => { totalFindings: 41, totalFailed: 30, totalPassed: 11, + postureScore: 26.8, }, { name: 'boo_type', totalFindings: 11, totalFailed: 5, totalPassed: 6, + postureScore: 54.5, }, ]); }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts index 8641a47c5e523..4f295ad7a2fdb 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts @@ -11,6 +11,7 @@ import type { QueryDslQueryContainer, SearchRequest, } from '@elastic/elasticsearch/lib/api/types'; +import { calculatePostureScore } from './get_stats'; import type { ComplianceDashboardData } from '../../../common/types'; import { KeyDocCount } from './compliance_dashboard'; @@ -25,12 +26,14 @@ export interface FailedFindingsBucket extends KeyDocCount { passed_findings: { doc_count: number; }; + score: { value: number }; } export const failedFindingsAggQuery = { aggs_by_resource_type: { terms: { field: 'rule.section', + size: 5, }, aggs: { failed_findings: { @@ -39,6 +42,22 @@ export const failedFindingsAggQuery = { passed_findings: { filter: { term: { 'result.evaluation': 'passed' } }, }, + score: { + bucket_script: { + buckets_path: { + passed: 'passed_findings>_count', + failed: 'failed_findings>_count', + }, + script: 'params.passed / (params.passed + params.failed)', + }, + }, + sort_by_score: { + bucket_sort: { + sort: { + score: 'asc' as 'asc', + }, + }, + }, }, }, }; @@ -55,12 +74,18 @@ export const getRisksEsQuery = (query: QueryDslQueryContainer, pitId: string): S export const getFailedFindingsFromAggs = ( queryResult: FailedFindingsBucket[] ): ComplianceDashboardData['groupedFindingsEvaluation'] => - queryResult.map((bucket) => ({ - name: bucket.key, - totalFindings: bucket.doc_count, - totalFailed: bucket.failed_findings.doc_count || 0, - totalPassed: bucket.passed_findings.doc_count || 0, - })); + queryResult.map((bucket) => { + const totalPassed = bucket.passed_findings.doc_count || 0; + const totalFailed = bucket.failed_findings.doc_count || 0; + + return { + name: bucket.key, + totalFindings: bucket.doc_count, + totalFailed, + totalPassed, + postureScore: calculatePostureScore(totalPassed, totalFailed), + }; + }); export const getGroupedFindingsEvaluation = async ( esClient: ElasticsearchClient, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index fb5af35a6fb33..bf3e06f2764ab 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9855,7 +9855,6 @@ "xpack.csp.cspSettings.rules": "Règles de sécurité du CSP - ", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "Niveau du cloud", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "Section CIS", - "xpack.csp.dashboard.risksTable.findingsColumnLabel": "Résultats", "xpack.csp.dashboard.risksTable.viewAllButtonTitle": "Afficher tous les échecs des résultats", "xpack.csp.dashboard.summarySection.cloudPostureScorePanelTitle": "Score du niveau du cloud", "xpack.csp.expandColumnDescriptionLabel": "Développer", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b792335f133ef..fe6bbcf474741 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9842,7 +9842,6 @@ "xpack.csp.cspSettings.rules": "CSPセキュリティルール - ", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "クラウド態勢", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CISセクション", - "xpack.csp.dashboard.risksTable.findingsColumnLabel": "調査結果", "xpack.csp.dashboard.risksTable.viewAllButtonTitle": "すべてのフィールド調査結果を表示", "xpack.csp.dashboard.summarySection.cloudPostureScorePanelTitle": "クラウド態勢スコア", "xpack.csp.expandColumnDescriptionLabel": "拡張", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f8ab94422d563..e28df61f1966d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9860,7 +9860,6 @@ "xpack.csp.cspSettings.rules": "CSP 安全规则 - ", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "云态势", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CIS 部分", - "xpack.csp.dashboard.risksTable.findingsColumnLabel": "结果", "xpack.csp.dashboard.risksTable.viewAllButtonTitle": "查看所有失败的结果", "xpack.csp.dashboard.summarySection.cloudPostureScorePanelTitle": "云态势分数", "xpack.csp.expandColumnDescriptionLabel": "展开",