From 09a00e0eb1bb9d373d7f94dbe0fa6ce50cfa1015 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 14 Nov 2023 17:12:53 -0500 Subject: [PATCH 01/17] scores by benchmark aggs --- .../server/tasks/findings_stats_task.ts | 55 +++++++++++++++++++ .../server/tasks/types.ts | 14 +++++ 2 files changed, 69 insertions(+) diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts index f40ce3f7dc4ab..9db1882ed6ebc 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts @@ -177,6 +177,39 @@ const getScoreQuery = (): SearchRequest => ({ }, }, }, + score_by_benchmark_id: { + terms: { + field: 'rule.benchmark.id', + }, + aggregations: { + benchmark_versions: { + terms: { + field: 'rule.benchmark.version', + }, + aggs: { + total_findings: { + value_count: { + field: 'result.evaluation', + }, + }, + passed_findings: { + filter: { + term: { + 'result.evaluation': 'passed', + }, + }, + }, + failed_findings: { + filter: { + term: { + 'result.evaluation': 'failed', + }, + }, + }, + }, + }, + }, + }, }, }, }, @@ -255,6 +288,27 @@ const getFindingsScoresDocIndexingPromises = ( ]; }) ); + // creating score per benchmark id and version + const benchmarkStats = Object.fromEntries( + policyTemplateTrend.score_by_benchmark_id.buckets.map((benchmarkIdBucket) => { + const benchmarkId = benchmarkIdBucket.key; + const benchmarkVersions = Object.fromEntries( + benchmarkIdBucket.benchmark_versions.buckets.map((benchmarkVersionBucket) => { + const benchmarkVersion = benchmarkVersionBucket.key.split('.').join('_'); + return [ + benchmarkVersion, + { + total_findings: benchmarkVersionBucket.total_findings.value, + passed_findings: benchmarkVersionBucket.passed_findings.doc_count, + failed_findings: benchmarkVersionBucket.failed_findings.doc_count, + }, + ]; + }) + ); + + return [benchmarkId, benchmarkVersions]; + }) + ); // each document contains the policy template and its scores return esClient.index({ @@ -265,6 +319,7 @@ const getFindingsScoresDocIndexingPromises = ( failed_findings: policyTemplateTrend.failed_findings.doc_count, total_findings: policyTemplateTrend.total_findings.value, score_by_cluster_id: clustersStats, + score_by_benchmark_id: benchmarkStats, }, }); }); diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/types.ts b/x-pack/plugins/cloud_security_posture/server/tasks/types.ts index 56ca619dcec55..839d4823ca47a 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/types.ts @@ -23,6 +23,20 @@ export interface ScoreByPolicyTemplateBucket { total_findings: { value: number }; }>; }; + score_by_benchmark_id: { + buckets: Array<{ + key: string; // benchmark id + doc_count: number; + benchmark_versions: { + buckets: Array<{ + key: string; // benchmark version + passed_findings: { doc_count: number }; + failed_findings: { doc_count: number }; + total_findings: { value: number }; + }>; + }; + }>; + }; }>; }; } From 81b7c2c9a54b073777cd58c7c93f53920e9bb195 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 14 Nov 2023 17:13:49 -0500 Subject: [PATCH 02/17] add benchmark aggregation query --- .../cloud_security_posture/common/types.ts | 13 ++ .../compliance_dashboard/get_benchmarks.ts | 145 ++++++++++++++++++ .../get_grouped_findings_evaluation.ts | 22 +++ .../routes/compliance_dashboard/get_trends.ts | 78 +++++++--- 4 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index 092129a6762e2..829f7dea6e4aa 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -74,10 +74,23 @@ export interface Cluster { trend: PostureTrend[]; } +export interface BenchmarkData { + meta: { + benchmarkId: CspFinding['rule']['benchmark']['id']; + benchmarkVersion: CspFinding['rule']['benchmark']['version']; + benchmarkName: CspFinding['rule']['benchmark']['name']; + assetCount: number; + }; + stats: Stats; + groupedFindingsEvaluation: GroupedFindingsEvaluation[]; + trend: PostureTrend[]; +} + export interface ComplianceDashboardData { stats: Stats; groupedFindingsEvaluation: GroupedFindingsEvaluation[]; clusters: Cluster[]; + benchmarks?: BenchmarkData[]; trend: PostureTrend[]; } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts new file mode 100644 index 0000000000000..6ea6adf618094 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -0,0 +1,145 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import type { + AggregationsMultiBucketAggregateBase as Aggregation, + QueryDslQueryContainer, + SearchRequest, +} from '@elastic/elasticsearch/lib/api/types'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { BenchmarkData } from '../../../common/types'; +import { + failedFindingsAggQuery, + BenchmarkVersionQueryResult, + getFailedFindingsFromAggs, +} from './get_grouped_findings_evaluation'; +import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats'; +import { KeyDocCount } from './compliance_dashboard'; +import { getIdentifierRuntimeMapping } from '../../../common/runtime_mappings/get_identifier_runtime_mapping'; + +export interface BenchmarkBucket extends KeyDocCount { + aggs_by_benchmark_version: Aggregation; +} + +interface BenchmarkQueryResult extends KeyDocCount { + aggs_by_benchmark: Aggregation; +} + +export type BenchmarkWithoutTrend = Omit; + +const MAX_BENCHMARKS = 500; + +export const getBenchmarksQuery = ( + query: QueryDslQueryContainer, + pitId: string, + runtimeMappings: MappingRuntimeFields +): SearchRequest => ({ + size: 0, + runtime_mappings: { ...runtimeMappings, ...getIdentifierRuntimeMapping() }, + query, + aggs: { + aggs_by_benchmarks: { + terms: { + field: 'rule.benchmark.id', + order: { + _count: 'desc', + }, + size: MAX_BENCHMARKS, + }, + aggs: { + aggs_by_benchmark_version: { + terms: { + field: 'rule.benchmark.version', + size: 100, + }, + aggs: { + aggs_by_benchmark_name: { + terms: { + field: 'rule.benchmark.name', + order: { + _count: 'desc', + }, + size: 100, + }, + }, + asset_count: { + cardinality: { + field: 'asset_identifier', + }, + }, + // Result evalution for passed or failed findings + ...findingsEvaluationAggsQuery, + // CIS Section Compliance Score and Failed Findings + ...failedFindingsAggQuery, + }, + }, + }, + }, + }, + pit: { + id: pitId, + }, +}); + +const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { + return benchmarks.flatMap((benchmarkAggregation: BenchmarkBucket) => { + const benchmarkId = benchmarkAggregation.key; + const versions = benchmarkAggregation.aggs_by_benchmark_version.buckets; + if (!Array.isArray(versions)) throw new Error('missing aggs by benchmark version'); + + return versions.map((version: BenchmarkVersionQueryResult) => { + const benchmarkVersion = version.key; + const assetCount = version.asset_count.value; + let benchmarkName = ''; + + if (!Array.isArray(version.aggs_by_benchmark_name.buckets)) + throw new Error('missing aggs by benchmark name'); + + if (version.aggs_by_benchmark_name && version.aggs_by_benchmark_name.buckets.length > 0) { + benchmarkName = version.aggs_by_benchmark_name.buckets[0].key; + } + const { passed_findings: passedFindings, failed_findings: failedFindings } = version; + const stats = getStatsFromFindingsEvaluationsAggs({ + passed_findings: passedFindings, + failed_findings: failedFindings, + }); + + const resourcesTypesAggs = version.aggs_by_resource_type.buckets; + if (!Array.isArray(resourcesTypesAggs)) + throw new Error('missing aggs by resource type per benchmark'); + const groupedFindingsEvaluation = getFailedFindingsFromAggs(resourcesTypesAggs); + + return { + meta: { + benchmarkId, + benchmarkVersion, + benchmarkName, + assetCount, + }, + stats, + groupedFindingsEvaluation, + }; + }); + }); +}; + +export const getBenchmarks = async ( + esClient: ElasticsearchClient, + query: QueryDslQueryContainer, + pitId: string, + runtimeMappings: MappingRuntimeFields +): Promise => { + const queryResult = await esClient.search( + getBenchmarksQuery(query, pitId, runtimeMappings) + ); + + const benchmarks = queryResult.aggregations?.aggs_by_benchmark.buckets; + if (!Array.isArray(benchmarks)) throw new Error('missing aggs by benchmark id'); + + return getBenchmarksFromAggs(benchmarks); +}; 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 239801350c7af..d30a394a36e24 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 @@ -20,6 +20,28 @@ export interface FailedFindingsQueryResult { aggs_by_resource_type: Aggregation; } +export interface BenchmarkVersionQueryResult extends KeyDocCount, FailedFindingsQueryResult { + failed_findings: { + doc_count: number; + }; + passed_findings: { + doc_count: number; + }; + asset_count: { + value: number; + }; + aggs_by_benchmark_name: Aggregation; +} +export interface FailedFindingsBucket extends KeyDocCount { + failed_findings: { + doc_count: number; + }; + passed_findings: { + doc_count: number; + }; + score: { value: number }; +} + export interface FailedFindingsBucket extends KeyDocCount { failed_findings: { doc_count: number; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts index cc8234fa6d7af..a8399bec8c5ae 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts @@ -23,12 +23,24 @@ export interface ScoreTrendDoc { failed_findings: number; } >; + score_by_benchmark_id: Record< + string, + Record< + string, + { + total_findings: number; + passed_findings: number; + failed_findings: number; + } + > + >; } export type Trends = Array<{ timestamp: string; summary: Stats; clusters: Record; + benchmarks: Record; }>; export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({ @@ -51,27 +63,51 @@ export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({ }, }); -export const getTrendsFromQueryResult = (scoreTrendDocs: ScoreTrendDoc[]): Trends => - scoreTrendDocs.map((data) => ({ - timestamp: data['@timestamp'], - summary: { - totalFindings: data.total_findings, - totalFailed: data.failed_findings, - totalPassed: data.passed_findings, - postureScore: calculatePostureScore(data.passed_findings, data.failed_findings), - }, - clusters: Object.fromEntries( - Object.entries(data.score_by_cluster_id).map(([clusterId, cluster]) => [ - clusterId, - { - totalFindings: cluster.total_findings, - totalFailed: cluster.failed_findings, - totalPassed: cluster.passed_findings, - postureScore: calculatePostureScore(cluster.passed_findings, cluster.failed_findings), - }, - ]) - ), - })); +export const getTrendsFromQueryResult = (scoreTrendDocs: ScoreTrendDoc[]): Trends => { + return scoreTrendDocs.map((data) => { + return { + timestamp: data['@timestamp'], + summary: { + totalFindings: data.total_findings, + totalFailed: data.failed_findings, + totalPassed: data.passed_findings, + postureScore: calculatePostureScore(data.passed_findings, data.failed_findings), + }, + clusters: Object.fromEntries( + Object.entries(data.score_by_cluster_id).map(([clusterId, cluster]) => [ + clusterId, + { + totalFindings: cluster.total_findings, + totalFailed: cluster.failed_findings, + totalPassed: cluster.passed_findings, + postureScore: calculatePostureScore(cluster.passed_findings, cluster.failed_findings), + }, + ]) + ), + benchmarks: data.score_by_benchmark_id + ? Object.fromEntries( + Object.entries(data.score_by_benchmark_id).flatMap(([benchmarkId, benchmark]) => + Object.entries(benchmark).map(([benchmarkVersion, benchmarkStats]) => { + const benchmarkVersionFieldFormat = benchmarkVersion.split('_').join('.'); + return [ + `${benchmarkId}_${benchmarkVersionFieldFormat}`, + { + totalFindings: benchmarkStats.total_findings, + totalFailed: benchmarkStats.failed_findings, + totalPassed: benchmarkStats.passed_findings, + postureScore: calculatePostureScore( + benchmarkStats.passed_findings, + benchmarkStats.failed_findings + ), + }, + ]; + }) + ) + ) + : {}, + }; + }); +}; export const getTrends = async ( esClient: ElasticsearchClient, From 190f5b8ca71b1d6e1ab65985cfbe5617669d8e6e Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 14 Nov 2023 17:15:35 -0500 Subject: [PATCH 03/17] first pass compliance dashboard api update --- .../compliance_dashboard.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index b827f5a31b4ee..ca053e098bd24 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -21,6 +21,7 @@ import { ClusterWithoutTrend, getClusters } from './get_clusters'; import { getStats } from './get_stats'; import { CspRouter } from '../../types'; import { getTrends, Trends } from './get_trends'; +import { BenchmarkWithoutTrend } from './get_benchmarks'; export interface KeyDocCount { key: TKey; @@ -36,6 +37,15 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends: })), })); +const getBenchmarksTrends = (benchmarksWithoutTrends: BenchmarkWithoutTrend[], trends: Trends) => + benchmarksWithoutTrends.map((benchmark) => ({ + ...benchmark, + trend: trends.map(({ timestamp, benchmarks: benchmarksTrendData }) => ({ + timestamp, + ...benchmarksTrendData[`${benchmark.meta.benchmarkId}_${benchmark.meta.benchmarkVersion}`], + })), + })); + const getSummaryTrend = (trends: Trends) => trends.map(({ timestamp, summary }) => ({ timestamp, ...summary })); @@ -77,13 +87,19 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }, }; - const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] = - await Promise.all([ - getStats(esClient, query, pitId, runtimeMappings), - getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings), - getClusters(esClient, query, pitId, runtimeMappings), - getTrends(esClient, policyTemplate), - ]); + const [ + stats, + groupedFindingsEvaluation, + clustersWithoutTrends, + // benchmarksWithoutTrends, + trends, + ] = await Promise.all([ + getStats(esClient, query, pitId, runtimeMappings), + getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings), + getClusters(esClient, query, pitId, runtimeMappings), + // getBenchmarks(esClient, query, pitId, runtimeMappings), + getTrends(esClient, policyTemplate), + ]); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive // ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request @@ -92,6 +108,9 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }); const clusters = getClustersTrends(clustersWithoutTrends, trends); + // const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); + // console.log(JSON.stringify(benchmarks)) + const trend = getSummaryTrend(trends); const body: ComplianceDashboardData = { From eaebc4072da2b0a8f55f6f63c8a5e98ef37b5143 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Mon, 20 Nov 2023 16:59:04 -0500 Subject: [PATCH 04/17] fix benchmarks api --- .../cloud_security_posture/common/types.ts | 2 +- .../compliance_dashboard.ts | 20 ++++++++++--------- .../compliance_dashboard/get_benchmarks.ts | 7 +------ 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index 829f7dea6e4aa..cf661958128bb 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -90,8 +90,8 @@ export interface ComplianceDashboardData { stats: Stats; groupedFindingsEvaluation: GroupedFindingsEvaluation[]; clusters: Cluster[]; - benchmarks?: BenchmarkData[]; trend: PostureTrend[]; + benchmarks: BenchmarkData[]; } export type CspStatusCode = diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index ca053e098bd24..82b30a78f142e 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -21,7 +21,7 @@ import { ClusterWithoutTrend, getClusters } from './get_clusters'; import { getStats } from './get_stats'; import { CspRouter } from '../../types'; import { getTrends, Trends } from './get_trends'; -import { BenchmarkWithoutTrend } from './get_benchmarks'; +import { BenchmarkWithoutTrend, getBenchmarks } from './get_benchmarks'; export interface KeyDocCount { key: TKey; @@ -40,10 +40,12 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends: const getBenchmarksTrends = (benchmarksWithoutTrends: BenchmarkWithoutTrend[], trends: Trends) => benchmarksWithoutTrends.map((benchmark) => ({ ...benchmark, - trend: trends.map(({ timestamp, benchmarks: benchmarksTrendData }) => ({ - timestamp, - ...benchmarksTrendData[`${benchmark.meta.benchmarkId}_${benchmark.meta.benchmarkVersion}`], - })), + trend: trends + .map(({ timestamp, benchmarks: benchmarksTrendData }) => ({ + timestamp, + ...benchmarksTrendData[`${benchmark.meta.benchmarkId}_${benchmark.meta.benchmarkVersion}`], + })) + .filter((doc) => Object.keys(doc).length > 1), })); const getSummaryTrend = (trends: Trends) => @@ -91,13 +93,13 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => stats, groupedFindingsEvaluation, clustersWithoutTrends, - // benchmarksWithoutTrends, + benchmarksWithoutTrends, trends, ] = await Promise.all([ getStats(esClient, query, pitId, runtimeMappings), getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings), getClusters(esClient, query, pitId, runtimeMappings), - // getBenchmarks(esClient, query, pitId, runtimeMappings), + getBenchmarks(esClient, query, pitId, runtimeMappings), getTrends(esClient, policyTemplate), ]); @@ -108,8 +110,7 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }); const clusters = getClustersTrends(clustersWithoutTrends, trends); - // const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); - // console.log(JSON.stringify(benchmarks)) + const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); const trend = getSummaryTrend(trends); @@ -117,6 +118,7 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => stats, groupedFindingsEvaluation, clusters, + benchmarks, trend, }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts index 6ea6adf618094..2ea9481df8411 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -43,7 +43,7 @@ export const getBenchmarksQuery = ( runtime_mappings: { ...runtimeMappings, ...getIdentifierRuntimeMapping() }, query, aggs: { - aggs_by_benchmarks: { + aggs_by_benchmark: { terms: { field: 'rule.benchmark.id', order: { @@ -55,16 +55,11 @@ export const getBenchmarksQuery = ( aggs_by_benchmark_version: { terms: { field: 'rule.benchmark.version', - size: 100, }, aggs: { aggs_by_benchmark_name: { terms: { field: 'rule.benchmark.name', - order: { - _count: 'desc', - }, - size: 100, }, }, asset_count: { From 58485f1173a38ca62ac1bb5fc9fff5941e9a4f50 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 21 Nov 2023 16:52:13 -0500 Subject: [PATCH 05/17] add utils for mapping field conversion --- .../create_indices/benchmark_score_mapping.ts | 1 + .../server/lib/mapping_field_util.ts | 28 +++++++++++++++++++ .../compliance_dashboard.ts | 16 +++++++---- .../routes/compliance_dashboard/get_trends.ts | 9 +++--- .../server/tasks/findings_stats_task.ts | 8 ++++-- 5 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts index 2f743e2ed8d64..50cc193abcef0 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts @@ -7,6 +7,7 @@ import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; export const benchmarkScoreMapping: MappingTypeMapping = { + dynamic: false, // TODO: before commit we need to verify this is the correct move properties: { '@timestamp': { type: 'date', diff --git a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts new file mode 100644 index 0000000000000..a4f42605810d9 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +export const DELIMITER = ';'; +export const MAPPING_VERSION_DELIMITER = '_'; +export const DOC_FIELD_VERSION_DELIMITER = '.'; + +export const toBenchmarkDocFieldKey = (benchmarkId: string, benchmarkVersion: string) => { + if (benchmarkVersion.includes(MAPPING_VERSION_DELIMITER)) + return `${benchmarkId}${DELIMITER}${benchmarkVersion.replaceAll( + `${MAPPING_VERSION_DELIMITER}`, + DOC_FIELD_VERSION_DELIMITER + )}`; + return `${benchmarkId}${DELIMITER}${benchmarkVersion}`; +}; + +export const toBenchmarkMappingFieldKey = (benchmarkId: string, benchmarkVersion: string) => { + if (benchmarkVersion.includes(DOC_FIELD_VERSION_DELIMITER)) + return `${benchmarkId}${DELIMITER}${benchmarkVersion.replaceAll( + `${DOC_FIELD_VERSION_DELIMITER}`, + MAPPING_VERSION_DELIMITER + )}`; + return `${benchmarkId}${DELIMITER}${benchmarkVersion}`; +}; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index 82b30a78f142e..94975cd5066c8 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -22,6 +22,7 @@ import { getStats } from './get_stats'; import { CspRouter } from '../../types'; import { getTrends, Trends } from './get_trends'; import { BenchmarkWithoutTrend, getBenchmarks } from './get_benchmarks'; +import { toBenchmarkDocFieldKey } from '../../lib/mapping_field_util'; export interface KeyDocCount { key: TKey; @@ -40,12 +41,16 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends: const getBenchmarksTrends = (benchmarksWithoutTrends: BenchmarkWithoutTrend[], trends: Trends) => benchmarksWithoutTrends.map((benchmark) => ({ ...benchmark, - trend: trends - .map(({ timestamp, benchmarks: benchmarksTrendData }) => ({ + trend: trends.map(({ timestamp, benchmarks: benchmarksTrendData }) => { + const benchmarkIdVersion = toBenchmarkDocFieldKey( + benchmark.meta.benchmarkId, + benchmark.meta.benchmarkName + ); + return { timestamp, - ...benchmarksTrendData[`${benchmark.meta.benchmarkId}_${benchmark.meta.benchmarkVersion}`], - })) - .filter((doc) => Object.keys(doc).length > 1), + ...benchmarksTrendData[benchmarkIdVersion], + }; + }), })); const getSummaryTrend = (trends: Trends) => @@ -111,7 +116,6 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => const clusters = getClustersTrends(clustersWithoutTrends, trends); const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); - const trend = getSummaryTrend(trends); const body: ComplianceDashboardData = { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts index a8399bec8c5ae..9fbfd639c3935 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts @@ -9,6 +9,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import { calculatePostureScore } from '../../../common/utils/helpers'; import { BENCHMARK_SCORE_INDEX_DEFAULT_NS } from '../../../common/constants'; import type { PosturePolicyTemplate, Stats } from '../../../common/types'; +import { toBenchmarkDocFieldKey } from '../../lib/mapping_field_util'; export interface ScoreTrendDoc { '@timestamp': string; @@ -45,8 +46,8 @@ export type Trends = Array<{ export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({ index: BENCHMARK_SCORE_INDEX_DEFAULT_NS, - // large number that should be sufficient for 24 hours considering we write to the score index every 5 minutes - size: 999, + // Amount of samples of the last 24 hours (accounting that we take a sample every 5 minutes) + size: (24 * 60) / 5, sort: '@timestamp:desc', query: { bool: { @@ -88,9 +89,9 @@ export const getTrendsFromQueryResult = (scoreTrendDocs: ScoreTrendDoc[]): Trend ? Object.fromEntries( Object.entries(data.score_by_benchmark_id).flatMap(([benchmarkId, benchmark]) => Object.entries(benchmark).map(([benchmarkVersion, benchmarkStats]) => { - const benchmarkVersionFieldFormat = benchmarkVersion.split('_').join('.'); + const benchmarkIdVersion = toBenchmarkDocFieldKey(benchmarkId, benchmarkVersion); return [ - `${benchmarkId}_${benchmarkVersionFieldFormat}`, + benchmarkIdVersion, { totalFindings: benchmarkStats.total_findings, totalFailed: benchmarkStats.failed_findings, diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts index 9db1882ed6ebc..c918a85e7a00e 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts @@ -32,6 +32,7 @@ import { type LatestTaskStateSchema, type TaskHealthStatus, } from './task_state'; +import { toBenchmarkMappingFieldKey } from '../lib/mapping_field_util'; const CSPM_FINDINGS_STATS_TASK_ID = 'cloud_security_posture-findings_stats'; const CSPM_FINDINGS_STATS_TASK_TYPE = 'cloud_security_posture-stats_task'; @@ -294,9 +295,12 @@ const getFindingsScoresDocIndexingPromises = ( const benchmarkId = benchmarkIdBucket.key; const benchmarkVersions = Object.fromEntries( benchmarkIdBucket.benchmark_versions.buckets.map((benchmarkVersionBucket) => { - const benchmarkVersion = benchmarkVersionBucket.key.split('.').join('_'); + const benchmarkIdVersion = toBenchmarkMappingFieldKey( + benchmarkId, + benchmarkVersionBucket.key + ); return [ - benchmarkVersion, + benchmarkIdVersion, { total_findings: benchmarkVersionBucket.total_findings.value, passed_findings: benchmarkVersionBucket.passed_findings.doc_count, From f91fd1751da93b66a0d68239e515838886232b6b Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 21 Nov 2023 20:38:08 -0500 Subject: [PATCH 06/17] add benchmark tests and unit tests --- .../server/lib/mapping_field_util.ts | 12 +- .../get_benchmarks.test.ts | 108 ++++++++++++++++++ .../compliance_dashboard/get_benchmarks.ts | 2 +- .../compliance_dashboard/get_trends.test.ts | 89 +++++++++------ .../server/tasks/findings_stats_task.ts | 7 +- 5 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts diff --git a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts index a4f42605810d9..a0e234ab8d090 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts @@ -18,11 +18,9 @@ export const toBenchmarkDocFieldKey = (benchmarkId: string, benchmarkVersion: st return `${benchmarkId}${DELIMITER}${benchmarkVersion}`; }; -export const toBenchmarkMappingFieldKey = (benchmarkId: string, benchmarkVersion: string) => { - if (benchmarkVersion.includes(DOC_FIELD_VERSION_DELIMITER)) - return `${benchmarkId}${DELIMITER}${benchmarkVersion.replaceAll( - `${DOC_FIELD_VERSION_DELIMITER}`, - MAPPING_VERSION_DELIMITER - )}`; - return `${benchmarkId}${DELIMITER}${benchmarkVersion}`; +export const toBenchmarkMappingFieldKey = (benchmarkVersion: string) => { + return `${benchmarkVersion.replace( + `/${DOC_FIELD_VERSION_DELIMITER}/g`, + MAPPING_VERSION_DELIMITER + )}`; }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts new file mode 100644 index 0000000000000..a066170333dac --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { BenchmarkBucket, getBenchmarksFromAggs } from './get_benchmarks'; + +const mockBenchmarkBuckets: BenchmarkBucket[] = [ + { + key: 'cis_aws', + doc_count: 12, + aggs_by_benchmark_version: { + buckets: [ + { + key: 'v1.5.0', + doc_count: 12, + asset_count: { + value: 1, + }, + aggs_by_resource_type: { + buckets: [ + { + key: 'foo_type', + doc_count: 6, + passed_findings: { + doc_count: 3, + }, + failed_findings: { + doc_count: 3, + }, + score: { + value: 0.5, + }, + }, + { + key: 'boo_type', + doc_count: 6, + passed_findings: { + doc_count: 3, + }, + failed_findings: { + doc_count: 3, + }, + score: { + value: 0.5, + }, + }, + ], + }, + aggs_by_benchmark_name: { + buckets: [ + { + key: 'CIS Amazon Web Services Foundations', + doc_count: 12, + }, + ], + }, + passed_findings: { + doc_count: 6, + }, + failed_findings: { + doc_count: 6, + }, + }, + ], + }, + }, +]; + +describe('getBenchmarksFromAggs', () => { + it('should return value matching ComplianceDashboardData["benchmarks"]', async () => { + const benchmarks = getBenchmarksFromAggs(mockBenchmarkBuckets); + expect(benchmarks).toEqual([ + { + meta: { + benchmarkId: 'cis_aws', + benchmarkVersion: 'v1.5.0', + benchmarkName: 'CIS Amazon Web Services Foundations', + assetCount: 1, + }, + stats: { + totalFindings: 12, + totalFailed: 6, + totalPassed: 6, + postureScore: 50.0, + }, + groupedFindingsEvaluation: [ + { + name: 'foo_type', + 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_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts index 2ea9481df8411..341d2a6857232 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -81,7 +81,7 @@ export const getBenchmarksQuery = ( }, }); -const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { +export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { return benchmarks.flatMap((benchmarkAggregation: BenchmarkBucket) => { const benchmarkId = benchmarkAggregation.key; const versions = benchmarkAggregation.aggs_by_benchmark_version.buckets; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts index 127cf3e1a3f80..73c9c8d450a9e 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts @@ -20,6 +20,15 @@ const trendDocs: ScoreTrendDoc[] = [ failed_findings: 15, }, }, + score_by_benchmark_id: { + cis_gcp: { + v2_0_0: { + total_findings: 6, + passed_findings: 3, + failed_findings: 3, + }, + }, + }, }, { '@timestamp': '2022-04-06T15:00:00Z', @@ -38,22 +47,27 @@ const trendDocs: ScoreTrendDoc[] = [ failed_findings: 5, }, }, - }, - { - '@timestamp': '2022-04-05T15:30:00Z', - total_findings: 30, - passed_findings: 25, - failed_findings: 5, - score_by_cluster_id: { - forth_cluster_id: { - total_findings: 25, - passed_findings: 25, - failed_findings: 0, + score_by_benchmark_id: { + cis_gcp: { + v2_0_0: { + total_findings: 6, + passed_findings: 3, + failed_findings: 3, + }, }, - fifth_cluster_id: { - total_findings: 5, - passed_findings: 0, - failed_findings: 5, + cis_azure: { + v2_0_0: { + total_findings: 6, + passed_findings: 3, + failed_findings: 3, + }, + }, + cis_aws: { + v1_5_0: { + total_findings: 6, + passed_findings: 3, + failed_findings: 3, + }, }, }, }, @@ -79,6 +93,14 @@ describe('getTrendsFromQueryResult', () => { postureScore: 25.0, }, }, + benchmarks: { + 'cis_gcp;v2.0.0': { + totalFailed: 3, + totalFindings: 6, + totalPassed: 3, + postureScore: 50, + }, + }, }, { timestamp: '2022-04-06T15:00:00Z', @@ -102,27 +124,24 @@ describe('getTrendsFromQueryResult', () => { postureScore: 75.0, }, }, - }, - { - timestamp: '2022-04-05T15:30:00Z', - summary: { - totalFindings: 30, - totalPassed: 25, - totalFailed: 5, - postureScore: 83.3, - }, - clusters: { - forth_cluster_id: { - totalFindings: 25, - totalPassed: 25, - totalFailed: 0, - postureScore: 100.0, + benchmarks: { + 'cis_gcp;v2.0.0': { + totalFailed: 3, + totalFindings: 6, + totalPassed: 3, + postureScore: 50.0, }, - fifth_cluster_id: { - totalFindings: 5, - totalPassed: 0, - totalFailed: 5, - postureScore: 0, + 'cis_azure;v2.0.0': { + totalFailed: 3, + totalFindings: 6, + totalPassed: 3, + postureScore: 50.0, + }, + 'cis_aws;v1.5.0': { + totalFailed: 3, + totalFindings: 6, + totalPassed: 3, + postureScore: 50.0, }, }, }, diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts index c918a85e7a00e..5820a4dcfedec 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts @@ -295,12 +295,9 @@ const getFindingsScoresDocIndexingPromises = ( const benchmarkId = benchmarkIdBucket.key; const benchmarkVersions = Object.fromEntries( benchmarkIdBucket.benchmark_versions.buckets.map((benchmarkVersionBucket) => { - const benchmarkIdVersion = toBenchmarkMappingFieldKey( - benchmarkId, - benchmarkVersionBucket.key - ); + const benchmarkVersion = toBenchmarkMappingFieldKey(benchmarkVersionBucket.key); return [ - benchmarkIdVersion, + benchmarkVersion, { total_findings: benchmarkVersionBucket.total_findings.value, passed_findings: benchmarkVersionBucket.passed_findings.doc_count, From c03ee4e351aab109099d291ece91f2210af43610 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Wed, 22 Nov 2023 19:35:56 -0500 Subject: [PATCH 07/17] add logger and tests --- .../server/lib/mapping_field_util.ts | 14 +- .../compliance_dashboard.ts | 24 +-- .../compliance_dashboard/get_benchmarks.ts | 20 ++- .../compliance_dashboard/get_clusters.ts | 20 ++- .../get_grouped_findings_evaluation.ts | 24 ++- .../routes/compliance_dashboard/get_stats.ts | 20 ++- .../compliance_dashboard/get_trends.test.ts | 4 +- .../routes/compliance_dashboard/get_trends.ts | 25 ++- .../test/cloud_security_posture_api/config.ts | 1 + .../routes/mocks/benchmark_score_mock.ts | 148 +++++++++++++++++ .../routes/mocks/findings_mock.ts | 59 +++++++ .../routes/stats.ts | 155 ++++++++++++++++++ 12 files changed, 456 insertions(+), 58 deletions(-) create mode 100644 x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts create mode 100644 x-pack/test/cloud_security_posture_api/routes/mocks/findings_mock.ts create mode 100644 x-pack/test/cloud_security_posture_api/routes/stats.ts diff --git a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts index a0e234ab8d090..aac9701cf082e 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts @@ -5,22 +5,14 @@ * 2.0. */ -export const DELIMITER = ';'; export const MAPPING_VERSION_DELIMITER = '_'; -export const DOC_FIELD_VERSION_DELIMITER = '.'; export const toBenchmarkDocFieldKey = (benchmarkId: string, benchmarkVersion: string) => { if (benchmarkVersion.includes(MAPPING_VERSION_DELIMITER)) - return `${benchmarkId}${DELIMITER}${benchmarkVersion.replaceAll( - `${MAPPING_VERSION_DELIMITER}`, - DOC_FIELD_VERSION_DELIMITER - )}`; - return `${benchmarkId}${DELIMITER}${benchmarkVersion}`; + return `${benchmarkId};${benchmarkVersion.replaceAll('_', '.')}`; + return `${benchmarkId};${benchmarkVersion}`; }; export const toBenchmarkMappingFieldKey = (benchmarkVersion: string) => { - return `${benchmarkVersion.replace( - `/${DOC_FIELD_VERSION_DELIMITER}/g`, - MAPPING_VERSION_DELIMITER - )}`; + return `${benchmarkVersion.replaceAll('.', '_')}`; }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index 94975cd5066c8..68b7457698e95 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -38,20 +38,22 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends: })), })); -const getBenchmarksTrends = (benchmarksWithoutTrends: BenchmarkWithoutTrend[], trends: Trends) => - benchmarksWithoutTrends.map((benchmark) => ({ +const getBenchmarksTrends = (benchmarksWithoutTrends: BenchmarkWithoutTrend[], trends: Trends) => { + return benchmarksWithoutTrends.map((benchmark) => ({ ...benchmark, trend: trends.map(({ timestamp, benchmarks: benchmarksTrendData }) => { const benchmarkIdVersion = toBenchmarkDocFieldKey( benchmark.meta.benchmarkId, - benchmark.meta.benchmarkName + benchmark.meta.benchmarkVersion ); + return { timestamp, ...benchmarksTrendData[benchmarkIdVersion], }; }), })); +}; const getSummaryTrend = (trends: Trends) => trends.map(({ timestamp, summary }) => ({ timestamp, ...summary })); @@ -73,6 +75,7 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }, async (context, request, response) => { const cspContext = await context.csp; + const logger = cspContext.logger; try { const esClient = cspContext.esClient.asCurrentUser; @@ -101,17 +104,17 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => benchmarksWithoutTrends, trends, ] = await Promise.all([ - getStats(esClient, query, pitId, runtimeMappings), - getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings), - getClusters(esClient, query, pitId, runtimeMappings), - getBenchmarks(esClient, query, pitId, runtimeMappings), - getTrends(esClient, policyTemplate), + getStats(logger, esClient, query, pitId, runtimeMappings), + getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), + getClusters(logger, esClient, query, pitId, runtimeMappings), + getBenchmarks(logger, esClient, query, pitId, runtimeMappings), + getTrends(logger, esClient, policyTemplate), ]); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive // ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request esClient.closePointInTime({ id: pitId }).catch((err) => { - cspContext.logger.warn(`Could not close PIT for stats endpoint: ${err}`); + logger.warn(`Could not close PIT for stats endpoint: ${err}`); }); const clusters = getClustersTrends(clustersWithoutTrends, trends); @@ -131,7 +134,8 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }); } catch (err) { const error = transformError(err); - cspContext.logger.error(`Error while fetching CSP stats: ${err}`); + logger.error(`Error while fetching CSP stats: ${err}`); + logger.error(err.stack); return response.customError({ body: { message: error.message }, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts index 341d2a6857232..08efc0d994ca6 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -11,6 +11,7 @@ import type { QueryDslQueryContainer, SearchRequest, } from '@elastic/elasticsearch/lib/api/types'; +import type { Logger } from '@kbn/core/server'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import type { BenchmarkData } from '../../../common/types'; import { @@ -124,17 +125,24 @@ export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { }; export const getBenchmarks = async ( + logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, runtimeMappings: MappingRuntimeFields ): Promise => { - const queryResult = await esClient.search( - getBenchmarksQuery(query, pitId, runtimeMappings) - ); + try { + const queryResult = await esClient.search( + getBenchmarksQuery(query, pitId, runtimeMappings) + ); - const benchmarks = queryResult.aggregations?.aggs_by_benchmark.buckets; - if (!Array.isArray(benchmarks)) throw new Error('missing aggs by benchmark id'); + const benchmarks = queryResult.aggregations?.aggs_by_benchmark.buckets; + if (!Array.isArray(benchmarks)) throw new Error('missing aggs by benchmark id'); - return getBenchmarksFromAggs(benchmarks); + return getBenchmarksFromAggs(benchmarks); + } catch (err) { + logger.error(`Failed to fetch benchmark stats ${err.message}`); + logger.error(err); + throw err; + } }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts index ae40f05258634..8fc13f69cc8fb 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts @@ -13,6 +13,7 @@ import type { AggregationsTopHitsAggregate, SearchHit, } from '@elastic/elasticsearch/lib/api/types'; +import type { Logger } from '@kbn/core/server'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import { CspFinding } from '../../../common/schemas/csp_finding'; import type { Cluster } from '../../../common/types'; @@ -109,17 +110,24 @@ export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTr }); export const getClusters = async ( + logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, runtimeMappings: MappingRuntimeFields ): Promise => { - const queryResult = await esClient.search( - getClustersQuery(query, pitId, runtimeMappings) - ); + try { + const queryResult = await esClient.search( + getClustersQuery(query, pitId, runtimeMappings) + ); - const clusters = queryResult.aggregations?.aggs_by_asset_identifier.buckets; - if (!Array.isArray(clusters)) throw new Error('missing aggs by cluster id'); + const clusters = queryResult.aggregations?.aggs_by_asset_identifier.buckets; + if (!Array.isArray(clusters)) throw new Error('missing aggs by cluster id'); - return getClustersFromAggs(clusters); + return getClustersFromAggs(clusters); + } catch (err) { + logger.error(`Failed to fetch cluster stats ${err.message}`); + logger.error(err); + throw err; + } }; 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 d30a394a36e24..4b8555dfd5404 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 type { Logger } from '@kbn/core/server'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import { calculatePostureScore } from '../../../common/utils/helpers'; import type { ComplianceDashboardData } from '../../../common/types'; @@ -118,19 +119,26 @@ export const getFailedFindingsFromAggs = ( }); export const getGroupedFindingsEvaluation = async ( + logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, runtimeMappings: MappingRuntimeFields ): Promise => { - const resourceTypesQueryResult = await esClient.search( - getRisksEsQuery(query, pitId, runtimeMappings) - ); + try { + const resourceTypesQueryResult = await esClient.search( + getRisksEsQuery(query, pitId, runtimeMappings) + ); - const ruleSections = resourceTypesQueryResult.aggregations?.aggs_by_resource_type.buckets; - if (!Array.isArray(ruleSections)) { - return []; - } + const ruleSections = resourceTypesQueryResult.aggregations?.aggs_by_resource_type.buckets; + if (!Array.isArray(ruleSections)) { + return []; + } - return getFailedFindingsFromAggs(ruleSections); + return getFailedFindingsFromAggs(ruleSections); + } catch (err) { + logger.error(`Failed to fetch findings stats ${err.message}`); + logger.error(err); + throw err; + } }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts index 2f0e1c1b17102..a9bddfc6a506f 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts @@ -8,6 +8,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { QueryDslQueryContainer, SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { Logger } from '@kbn/core/server'; import { calculatePostureScore } from '../../../common/utils/helpers'; import type { ComplianceDashboardData } from '../../../common/types'; @@ -78,17 +79,24 @@ export const getStatsFromFindingsEvaluationsAggs = ( }; export const getStats = async ( + logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, runtimeMappings: MappingRuntimeFields ): Promise => { - const evaluationsQueryResult = await esClient.search( - getEvaluationsQuery(query, pitId, runtimeMappings) - ); + try { + const evaluationsQueryResult = await esClient.search( + getEvaluationsQuery(query, pitId, runtimeMappings) + ); - const findingsEvaluations = evaluationsQueryResult.aggregations; - if (!findingsEvaluations) throw new Error('missing findings evaluations'); + const findingsEvaluations = evaluationsQueryResult.aggregations; + if (!findingsEvaluations) throw new Error('missing findings evaluations'); - return getStatsFromFindingsEvaluationsAggs(findingsEvaluations); + return getStatsFromFindingsEvaluationsAggs(findingsEvaluations); + } catch (err) { + logger.error(`Failed to fetch stats ${err.message}`); + logger.error(err); + throw err; + } }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts index 73c9c8d450a9e..f26760221292b 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getTrendsFromQueryResult, ScoreTrendDoc } from './get_trends'; +import { formatTrends, ScoreTrendDoc } from './get_trends'; const trendDocs: ScoreTrendDoc[] = [ { @@ -75,7 +75,7 @@ const trendDocs: ScoreTrendDoc[] = [ describe('getTrendsFromQueryResult', () => { it('should return value matching Trends type definition, in descending order, and with postureScore', async () => { - const trends = getTrendsFromQueryResult(trendDocs); + const trends = formatTrends(trendDocs); expect(trends).toEqual([ { timestamp: '2022-04-06T15:30:00Z', diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts index 9fbfd639c3935..a74ebdd99fa98 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; +import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { calculatePostureScore } from '../../../common/utils/helpers'; import { BENCHMARK_SCORE_INDEX_DEFAULT_NS } from '../../../common/constants'; import type { PosturePolicyTemplate, Stats } from '../../../common/types'; @@ -64,7 +64,7 @@ export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({ }, }); -export const getTrendsFromQueryResult = (scoreTrendDocs: ScoreTrendDoc[]): Trends => { +export const formatTrends = (scoreTrendDocs: ScoreTrendDoc[]): Trends => { return scoreTrendDocs.map((data) => { return { timestamp: data['@timestamp'], @@ -111,17 +111,24 @@ export const getTrendsFromQueryResult = (scoreTrendDocs: ScoreTrendDoc[]): Trend }; export const getTrends = async ( + logger: Logger, esClient: ElasticsearchClient, policyTemplate: PosturePolicyTemplate ): Promise => { - const trendsQueryResult = await esClient.search(getTrendsQuery(policyTemplate)); + try { + const trendsQueryResult = await esClient.search(getTrendsQuery(policyTemplate)); - if (!trendsQueryResult.hits.hits) throw new Error('missing trend results from score index'); + if (!trendsQueryResult.hits.hits) throw new Error('missing trend results from score index'); - const scoreTrendDocs = trendsQueryResult.hits.hits.map((hit) => { - if (!hit._source) throw new Error('missing _source data for one or more of trend results'); - return hit._source; - }); + const scoreTrendDocs = trendsQueryResult.hits.hits.map((hit) => { + if (!hit._source) throw new Error('missing _source data for one or more of trend results'); + return hit._source; + }); - return getTrendsFromQueryResult(scoreTrendDocs); + return formatTrends(scoreTrendDocs); + } catch (err) { + logger.error(`Failed to fetch trendlines data ${err.message}`); + logger.error(err); + throw err; + } }; diff --git a/x-pack/test/cloud_security_posture_api/config.ts b/x-pack/test/cloud_security_posture_api/config.ts index a206fd563cc00..e7a34bafe9fb5 100644 --- a/x-pack/test/cloud_security_posture_api/config.ts +++ b/x-pack/test/cloud_security_posture_api/config.ts @@ -17,6 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./telemetry/telemetry.ts'), require.resolve('./routes/vulnerabilities_dashboard.ts'), + require.resolve('./routes/stats.ts'), ], junit: { reportName: 'X-Pack Cloud Security Posture API Tests', diff --git a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts new file mode 100644 index 0000000000000..37abfbd6c0ceb --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts @@ -0,0 +1,148 @@ +/* + * 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. + */ + +export const benchmarkScoreMockData = [ + { + total_findings: 1, + policy_template: 'cspm', + '@timestamp': '2023-11-22T16:10:55.229268215Z', + score_by_cluster_id: { + 'Another Upper case account id': { + total_findings: 1, + passed_findings: 1, + failed_findings: 0, + }, + }, + score_by_benchmark_id: { + cis_gcp: { + v2_0_0: { + total_findings: 1196, + passed_findings: 162, + failed_findings: 1034, + }, + }, + cis_azure: { + v2_0_0: { + total_findings: 132, + passed_findings: 68, + failed_findings: 64, + }, + }, + cis_aws: { + v1_5_0: { + total_findings: 1, + passed_findings: 1, + failed_findings: 0, + }, + }, + }, + passed_findings: 1, + failed_findings: 0, + }, +]; + +export const complianceDashboardDataMock = { + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + resourcesEvaluated: 1, + }, + groupedFindingsEvaluation: [ + { + name: 'Another upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + clusters: [ + { + meta: { + clusterId: 'Another Upper case account id', + assetIdentifierId: 'Another Upper case account id', + benchmark: { + name: 'CIS AWS2', + id: 'cis_aws', + posture_type: 'cspm', + version: 'v1.5.0', + }, + cloud: { + account: { + id: 'Another Upper case account id', + }, + }, + }, + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + }, + groupedFindingsEvaluation: [ + { + name: 'Another upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + }, + ], + benchmarks: [ + { + meta: { + benchmarkId: 'cis_aws', + benchmarkVersion: 'v1.5.0', + benchmarkName: 'CIS AWS2', + assetCount: 1, + }, + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + }, + groupedFindingsEvaluation: [ + { + name: 'Another upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], +}; diff --git a/x-pack/test/cloud_security_posture_api/routes/mocks/findings_mock.ts b/x-pack/test/cloud_security_posture_api/routes/mocks/findings_mock.ts new file mode 100644 index 0000000000000..92ca03b1e4789 --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/mocks/findings_mock.ts @@ -0,0 +1,59 @@ +/* + * 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 Chance from 'chance'; + +const chance = new Chance(); + +export const findingsMockData = [ + { + '@timestamp': '2023-06-29T02:08:44.993Z', + resource: { + id: chance.guid(), + name: `kubelet`, + sub_type: 'lower case sub type', + type: 'k8s_resource_type', + }, + result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' }, + rule: { + name: 'Upper case rule name', + section: 'Upper case section', + benchmark: { + id: 'cis_k8s', + posture_type: 'kspm', + name: 'CIS Kubernetes V1.23', + version: 'v1.0.0', + }, + }, + orchestrator: { + cluster: { id: 'Upper case cluster id' }, + }, + }, + { + '@timestamp': '2023-06-29T02:08:44.993Z', + resource: { + id: chance.guid(), + name: `Pod`, + sub_type: 'Upper case sub type', + type: 'cloud_resource_type', + }, + result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' }, + rule: { + name: 'lower case rule name', + section: 'Another upper case section', + benchmark: { + id: 'cis_aws', + posture_type: 'cspm', + name: 'CIS AWS2', + version: 'v1.5.0', + }, + }, + cloud: { + account: { id: 'Another Upper case account id' }, + }, + }, +]; diff --git a/x-pack/test/cloud_security_posture_api/routes/stats.ts b/x-pack/test/cloud_security_posture_api/routes/stats.ts new file mode 100644 index 0000000000000..e79e571740951 --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/stats.ts @@ -0,0 +1,155 @@ +/* + * 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { + BENCHMARK_SCORE_INDEX_DEFAULT_NS, + LATEST_FINDINGS_INDEX_DEFAULT_NS, +} from '@kbn/cloud-security-posture-plugin/common/constants'; +import { + ComplianceDashboardData, + PostureTrend, +} from '@kbn/cloud-security-posture-plugin/common/types'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { benchmarkScoreMockData, complianceDashboardDataMock } from './mocks/benchmark_score_mock'; +import { findingsMockData } from './mocks/findings_mock'; + +// eslint-disable-next-line import/no-default-export +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + + const kibanaHttpClient = getService('supertest'); + + const retry = getService('retry'); + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + /** + * required before indexing findings + */ + const waitForPluginInitialized = (): Promise => + retry.try(async () => { + log.debug('Check CSP plugin is initialized'); + const response = await supertest + .get('/internal/cloud_security_posture/status?check=init') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .expect(200); + expect(response.body).to.eql({ isPluginInitialized: true }); + log.debug('CSP plugin is initialized'); + }); + + const index = { + addFindings: async (findingsMock: T[]) => { + await Promise.all( + findingsMock.map((findingsDoc) => + es.index({ + index: LATEST_FINDINGS_INDEX_DEFAULT_NS, + body: { ...findingsDoc, '@timestamp': new Date().toISOString() }, + refresh: true, + }) + ) + ); + }, + + addScores: async (scoresMock: T[]) => { + await Promise.all( + scoresMock.map((scoreDoc) => + es.index({ + index: BENCHMARK_SCORE_INDEX_DEFAULT_NS, + body: { ...scoreDoc, '@timestamp': new Date().toISOString() }, + refresh: true, + }) + ) + ); + }, + + removeFindings: async () => { + const indexExists = await es.indices.exists({ index: LATEST_FINDINGS_INDEX_DEFAULT_NS }); + + if (indexExists) { + es.deleteByQuery({ + index: LATEST_FINDINGS_INDEX_DEFAULT_NS, + query: { match_all: {} }, + refresh: true, + }); + } + }, + + removeScores: async () => { + const indexExists = await es.indices.exists({ index: BENCHMARK_SCORE_INDEX_DEFAULT_NS }); + + if (indexExists) { + es.deleteByQuery({ + index: BENCHMARK_SCORE_INDEX_DEFAULT_NS, + query: { match_all: {} }, + refresh: true, + }); + } + }, + + deleteFindingsIndex: async () => { + const indexExists = await es.indices.exists({ index: LATEST_FINDINGS_INDEX_DEFAULT_NS }); + + if (indexExists) { + await es.indices.delete({ index: LATEST_FINDINGS_INDEX_DEFAULT_NS }); + } + }, + }; + + describe('GET /internal/cloud_security_posture/stats', () => { + describe('benchmarks', async () => { + beforeEach(async () => { + await index.removeFindings(); + await index.removeScores(); + + await waitForPluginInitialized(); + await index.addScores(benchmarkScoreMockData); + await index.addFindings([findingsMockData[1]]); + }); + + it('should return CSPM benchmarks ', async () => { + const { body: res }: { body: ComplianceDashboardData } = await kibanaHttpClient + .get(`/internal/cloud_security_posture/stats/cspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .expect(200); + + const removeRealtimeCalculatedFields = (trends: PostureTrend[]) => { + return trends.map((trend: PostureTrend) => { + const { timestamp, ...rest } = trend; + return rest; + }); + }; + + const resBenchmarks = res.benchmarks.flatMap((benchmark) => ({ + ...benchmark, + trend: removeRealtimeCalculatedFields(benchmark.trend), + })); + + const resClusters = res.clusters.flatMap((cluster) => { + const clusterWithoutTrend = { + ...cluster, + trend: removeRealtimeCalculatedFields(cluster.trend), + }; + const { lastUpdate, ...clusterWithoutTime } = clusterWithoutTrend.meta; + + return { ...clusterWithoutTrend, meta: clusterWithoutTime }; + }); + + const trends = removeRealtimeCalculatedFields(res.trend); + + expect({ + ...res, + clusters: resClusters, + benchmarks: resBenchmarks, + trend: trends, + }).to.eql(complianceDashboardDataMock); + }); + }); + }); +} From f33f65e51deeae1d7cacf799c7f45e9c61e9bae2 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Mon, 27 Nov 2023 09:51:01 -0500 Subject: [PATCH 08/17] add remove the unecessary mock items --- .../routes/mocks/benchmark_score_mock.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts index 37abfbd6c0ceb..921e30fb93b3a 100644 --- a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts +++ b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts @@ -18,20 +18,6 @@ export const benchmarkScoreMockData = [ }, }, score_by_benchmark_id: { - cis_gcp: { - v2_0_0: { - total_findings: 1196, - passed_findings: 162, - failed_findings: 1034, - }, - }, - cis_azure: { - v2_0_0: { - total_findings: 132, - passed_findings: 68, - failed_findings: 64, - }, - }, cis_aws: { v1_5_0: { total_findings: 1, From e4c84fed9ce1315505fca17b9ac75992018a3ef6 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Mon, 27 Nov 2023 10:30:25 -0500 Subject: [PATCH 09/17] add kspm test case --- .../routes/mocks/benchmark_score_mock.ts | 118 +++++++++++++++++- .../routes/stats.ts | 93 ++++++++++---- 2 files changed, 182 insertions(+), 29 deletions(-) diff --git a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts index 921e30fb93b3a..3e573fa837ed1 100644 --- a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts +++ b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts @@ -5,10 +5,10 @@ * 2.0. */ -export const benchmarkScoreMockData = [ +export const getBenchmarkScoreMockData = (postureType: string) => [ { total_findings: 1, - policy_template: 'cspm', + policy_template: postureType, '@timestamp': '2023-11-22T16:10:55.229268215Z', score_by_cluster_id: { 'Another Upper case account id': { @@ -16,6 +16,11 @@ export const benchmarkScoreMockData = [ passed_findings: 1, failed_findings: 0, }, + 'Upper case cluster id': { + total_findings: 1, + passed_findings: 1, + failed_findings: 0, + }, }, score_by_benchmark_id: { cis_aws: { @@ -25,13 +30,20 @@ export const benchmarkScoreMockData = [ failed_findings: 0, }, }, + cis_k8s: { + v1_0_0: { + total_findings: 1, + passed_findings: 1, + failed_findings: 0, + }, + }, }, passed_findings: 1, failed_findings: 0, }, ]; -export const complianceDashboardDataMock = { +export const cspmComplianceDashboardDataMock = { stats: { totalFailed: 0, totalPassed: 1, @@ -132,3 +144,103 @@ export const complianceDashboardDataMock = { }, ], }; + +export const kspmComplianceDashboardDataMock = { + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + resourcesEvaluated: 1, + }, + groupedFindingsEvaluation: [ + { + name: 'Upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + clusters: [ + { + meta: { + clusterId: 'Upper case cluster id', + assetIdentifierId: 'Upper case cluster id', + benchmark: { + name: 'CIS Kubernetes V1.23', + id: 'cis_k8s', + posture_type: 'kspm', + version: 'v1.0.0', + }, + cluster: { + id: 'Upper case cluster id', + }, + }, + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + }, + groupedFindingsEvaluation: [ + { + name: 'Upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + }, + ], + benchmarks: [ + { + meta: { + benchmarkId: 'cis_k8s', + benchmarkVersion: 'v1.0.0', + benchmarkName: 'CIS Kubernetes V1.23', + assetCount: 1, + }, + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + }, + groupedFindingsEvaluation: [ + { + name: 'Upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], + }, + ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], +}; diff --git a/x-pack/test/cloud_security_posture_api/routes/stats.ts b/x-pack/test/cloud_security_posture_api/routes/stats.ts index e79e571740951..04a43b7e0e4e4 100644 --- a/x-pack/test/cloud_security_posture_api/routes/stats.ts +++ b/x-pack/test/cloud_security_posture_api/routes/stats.ts @@ -15,9 +15,44 @@ import { } from '@kbn/cloud-security-posture-plugin/common/types'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; -import { benchmarkScoreMockData, complianceDashboardDataMock } from './mocks/benchmark_score_mock'; +import { + getBenchmarkScoreMockData, + cspmComplianceDashboardDataMock, + kspmComplianceDashboardDataMock, +} from './mocks/benchmark_score_mock'; import { findingsMockData } from './mocks/findings_mock'; +const removeRealtimeCalculatedFields = (trends: PostureTrend[]) => { + return trends.map((trend: PostureTrend) => { + const { timestamp, ...rest } = trend; + return rest; + }); +}; + +const getNonTimestampResponseFields = (res: ComplianceDashboardData) => { + const resBenchmarks = res.benchmarks.flatMap((benchmark) => ({ + ...benchmark, + trend: removeRealtimeCalculatedFields(benchmark.trend), + })); + + const resClusters = res.clusters.flatMap((cluster) => { + const clusterWithoutTrend = { + ...cluster, + trend: removeRealtimeCalculatedFields(cluster.trend), + }; + const { lastUpdate, ...clusterWithoutTime } = clusterWithoutTrend.meta; + + return { ...clusterWithoutTrend, meta: clusterWithoutTime }; + }); + + const trends = removeRealtimeCalculatedFields(res.trend); + + return { + resBenchmarks, + resClusters, + trends, + }; +}; // eslint-disable-next-line import/no-default-export export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; @@ -102,13 +137,13 @@ export default function (providerContext: FtrProviderContext) { }; describe('GET /internal/cloud_security_posture/stats', () => { - describe('benchmarks', async () => { + describe('cspm benchmarks', async () => { beforeEach(async () => { await index.removeFindings(); await index.removeScores(); await waitForPluginInitialized(); - await index.addScores(benchmarkScoreMockData); + await index.addScores(getBenchmarkScoreMockData('cspm')); await index.addFindings([findingsMockData[1]]); }); @@ -119,36 +154,42 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(200); - const removeRealtimeCalculatedFields = (trends: PostureTrend[]) => { - return trends.map((trend: PostureTrend) => { - const { timestamp, ...rest } = trend; - return rest; - }); - }; - - const resBenchmarks = res.benchmarks.flatMap((benchmark) => ({ - ...benchmark, - trend: removeRealtimeCalculatedFields(benchmark.trend), - })); - - const resClusters = res.clusters.flatMap((cluster) => { - const clusterWithoutTrend = { - ...cluster, - trend: removeRealtimeCalculatedFields(cluster.trend), - }; - const { lastUpdate, ...clusterWithoutTime } = clusterWithoutTrend.meta; - - return { ...clusterWithoutTrend, meta: clusterWithoutTime }; - }); + const { resClusters, resBenchmarks, trends } = getNonTimestampResponseFields(res); + + expect({ + ...res, + clusters: resClusters, + benchmarks: resBenchmarks, + trend: trends, + }).to.eql(cspmComplianceDashboardDataMock); + }); + }); + + describe('kspm benchmarks', async () => { + beforeEach(async () => { + await index.removeFindings(); + await index.removeScores(); + + await waitForPluginInitialized(); + await index.addScores(getBenchmarkScoreMockData('kspm')); + await index.addFindings([findingsMockData[0]]); + }); + + it('should return KSPM benchmarks ', async () => { + const { body: res }: { body: ComplianceDashboardData } = await kibanaHttpClient + .get(`/internal/cloud_security_posture/stats/kspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .expect(200); - const trends = removeRealtimeCalculatedFields(res.trend); + const { resClusters, resBenchmarks, trends } = getNonTimestampResponseFields(res); expect({ ...res, clusters: resClusters, benchmarks: resBenchmarks, trend: trends, - }).to.eql(complianceDashboardDataMock); + }).to.eql(kspmComplianceDashboardDataMock); }); }); }); From 605c38f8ec1bf93c9fbeb555887c45c61e0a30e4 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Mon, 27 Nov 2023 20:03:33 -0500 Subject: [PATCH 10/17] remove dynamic property --- .../server/create_indices/benchmark_score_mapping.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts index 50cc193abcef0..2f743e2ed8d64 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/benchmark_score_mapping.ts @@ -7,7 +7,6 @@ import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; export const benchmarkScoreMapping: MappingTypeMapping = { - dynamic: false, // TODO: before commit we need to verify this is the correct move properties: { '@timestamp': { type: 'date', From 622da00b7070972a64e153a8ab1a3c615073b0d4 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Tue, 28 Nov 2023 18:32:06 -0500 Subject: [PATCH 11/17] add versioning and update integration tests --- .../cloud_security_posture/common/types.ts | 6 ++ .../compliance_dashboard.ts | 95 ++++++++++++++++--- .../get_benchmarks.test.ts | 2 +- .../routes/mocks/benchmark_score_mock.ts | 58 ++++++++++- .../routes/stats.ts | 83 +++++++++++----- 5 files changed, 201 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index cf661958128bb..c5c7274e191d5 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -91,6 +91,12 @@ export interface ComplianceDashboardData { groupedFindingsEvaluation: GroupedFindingsEvaluation[]; clusters: Cluster[]; trend: PostureTrend[]; +} + +export interface ComplianceDashboardDataV2 { + stats: Stats; + groupedFindingsEvaluation: GroupedFindingsEvaluation[]; + trend: PostureTrend[]; benchmarks: BenchmarkData[]; } diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index 68b7457698e95..be51ee10cacc0 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -14,6 +14,7 @@ import type { PosturePolicyTemplate, ComplianceDashboardData, GetComplianceDashboardRequest, + ComplianceDashboardDataV2, } from '../../../common/types'; import { LATEST_FINDINGS_INDEX_DEFAULT_NS, STATS_ROUTE_PATH } from '../../../common/constants'; import { getGroupedFindingsEvaluation } from './get_grouped_findings_evaluation'; @@ -97,19 +98,13 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }, }; - const [ - stats, - groupedFindingsEvaluation, - clustersWithoutTrends, - benchmarksWithoutTrends, - trends, - ] = await Promise.all([ - getStats(logger, esClient, query, pitId, runtimeMappings), - getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), - getClusters(logger, esClient, query, pitId, runtimeMappings), - getBenchmarks(logger, esClient, query, pitId, runtimeMappings), - getTrends(logger, esClient, policyTemplate), - ]); + const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] = + await Promise.all([ + getStats(logger, esClient, query, pitId, runtimeMappings), + getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), + getClusters(logger, esClient, query, pitId, runtimeMappings), + getTrends(logger, esClient, policyTemplate), + ]); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive // ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request @@ -118,14 +113,12 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => }); const clusters = getClustersTrends(clustersWithoutTrends, trends); - const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); const trend = getSummaryTrend(trends); const body: ComplianceDashboardData = { stats, groupedFindingsEvaluation, clusters, - benchmarks, trend, }; @@ -137,6 +130,78 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => logger.error(`Error while fetching CSP stats: ${err}`); logger.error(err.stack); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ) + .addVersion( + { + version: '2', + validate: { + request: { + params: getComplianceDashboardSchema, + }, + }, + }, + async (context, request, response) => { + const cspContext = await context.csp; + const logger = cspContext.logger; + + try { + const esClient = cspContext.esClient.asCurrentUser; + + const { id: pitId } = await esClient.openPointInTime({ + index: LATEST_FINDINGS_INDEX_DEFAULT_NS, + keep_alive: '30s', + }); + + const params: GetComplianceDashboardRequest = request.params; + const policyTemplate = params.policy_template as PosturePolicyTemplate; + + // runtime mappings create the `safe_posture_type` field, which equals to `kspm` or `cspm` based on the value and existence of the `posture_type` field which was introduced at 8.7 + // the `query` is then being passed to our getter functions to filter per posture type even for older findings before 8.7 + const runtimeMappings: MappingRuntimeFields = getSafePostureTypeRuntimeMapping(); + const query: QueryDslQueryContainer = { + bool: { + filter: [{ term: { safe_posture_type: policyTemplate } }], + }, + }; + + const [stats, groupedFindingsEvaluation, benchmarksWithoutTrends, trends] = + await Promise.all([ + getStats(logger, esClient, query, pitId, runtimeMappings), + getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), + getBenchmarks(logger, esClient, query, pitId, runtimeMappings), + getTrends(logger, esClient, policyTemplate), + ]); + + // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive + // ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request + esClient.closePointInTime({ id: pitId }).catch((err) => { + logger.warn(`Could not close PIT for stats endpoint: ${err}`); + }); + + const benchmarks = getBenchmarksTrends(benchmarksWithoutTrends, trends); + const trend = getSummaryTrend(trends); + + const body: ComplianceDashboardDataV2 = { + stats, + groupedFindingsEvaluation, + benchmarks, + trend, + }; + + return response.ok({ + body, + }); + } catch (err) { + const error = transformError(err); + logger.error(`Error while fetching v2 CSP stats: ${err}`); + logger.error(err.stack); + return response.customError({ body: { message: error.message }, statusCode: error.statusCode, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts index a066170333dac..cf4d1632a6b50 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.test.ts @@ -70,7 +70,7 @@ const mockBenchmarkBuckets: BenchmarkBucket[] = [ ]; describe('getBenchmarksFromAggs', () => { - it('should return value matching ComplianceDashboardData["benchmarks"]', async () => { + it('should return value matching ComplianceDashboardDataV2["benchmarks"]', async () => { const benchmarks = getBenchmarksFromAggs(mockBenchmarkBuckets); expect(benchmarks).toEqual([ { diff --git a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts index 3e573fa837ed1..f24c960783e53 100644 --- a/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts +++ b/x-pack/test/cloud_security_posture_api/routes/mocks/benchmark_score_mock.ts @@ -43,7 +43,7 @@ export const getBenchmarkScoreMockData = (postureType: string) => [ }, ]; -export const cspmComplianceDashboardDataMock = { +export const cspmComplianceDashboardDataMockV1 = { stats: { totalFailed: 0, totalPassed: 1, @@ -102,6 +102,33 @@ export const cspmComplianceDashboardDataMock = { ], }, ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], +}; + +export const cspmComplianceDashboardDataMockV2 = { + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + resourcesEvaluated: 1, + }, + groupedFindingsEvaluation: [ + { + name: 'Another upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], benchmarks: [ { meta: { @@ -145,7 +172,7 @@ export const cspmComplianceDashboardDataMock = { ], }; -export const kspmComplianceDashboardDataMock = { +export const kspmComplianceDashboardDataMockV1 = { stats: { totalFailed: 0, totalPassed: 1, @@ -202,6 +229,33 @@ export const kspmComplianceDashboardDataMock = { ], }, ], + trend: [ + { + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], +}; + +export const kspmComplianceDashboardDataMockV2 = { + stats: { + totalFailed: 0, + totalPassed: 1, + totalFindings: 1, + postureScore: 100, + resourcesEvaluated: 1, + }, + groupedFindingsEvaluation: [ + { + name: 'Upper case section', + totalFindings: 1, + totalFailed: 0, + totalPassed: 1, + postureScore: 100, + }, + ], benchmarks: [ { meta: { diff --git a/x-pack/test/cloud_security_posture_api/routes/stats.ts b/x-pack/test/cloud_security_posture_api/routes/stats.ts index 04a43b7e0e4e4..92dac0d6b0277 100644 --- a/x-pack/test/cloud_security_posture_api/routes/stats.ts +++ b/x-pack/test/cloud_security_posture_api/routes/stats.ts @@ -10,15 +10,20 @@ import { LATEST_FINDINGS_INDEX_DEFAULT_NS, } from '@kbn/cloud-security-posture-plugin/common/constants'; import { + BenchmarkData, + Cluster, ComplianceDashboardData, + ComplianceDashboardDataV2, PostureTrend, } from '@kbn/cloud-security-posture-plugin/common/types'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; import { getBenchmarkScoreMockData, - cspmComplianceDashboardDataMock, - kspmComplianceDashboardDataMock, + kspmComplianceDashboardDataMockV1, + kspmComplianceDashboardDataMockV2, + cspmComplianceDashboardDataMockV1, + cspmComplianceDashboardDataMockV2, } from './mocks/benchmark_score_mock'; import { findingsMockData } from './mocks/findings_mock'; @@ -29,13 +34,8 @@ const removeRealtimeCalculatedFields = (trends: PostureTrend[]) => { }); }; -const getNonTimestampResponseFields = (res: ComplianceDashboardData) => { - const resBenchmarks = res.benchmarks.flatMap((benchmark) => ({ - ...benchmark, - trend: removeRealtimeCalculatedFields(benchmark.trend), - })); - - const resClusters = res.clusters.flatMap((cluster) => { +const removeRealtimeClusterFields = (clusters: Cluster[]) => + clusters.flatMap((cluster) => { const clusterWithoutTrend = { ...cluster, trend: removeRealtimeCalculatedFields(cluster.trend), @@ -45,14 +45,12 @@ const getNonTimestampResponseFields = (res: ComplianceDashboardData) => { return { ...clusterWithoutTrend, meta: clusterWithoutTime }; }); - const trends = removeRealtimeCalculatedFields(res.trend); +const removeRealtimeBenchmarkFields = (benchmarks: BenchmarkData[]) => + benchmarks.flatMap((benchmark) => ({ + ...benchmark, + trend: removeRealtimeCalculatedFields(benchmark.trend), + })); - return { - resBenchmarks, - resClusters, - trends, - }; -}; // eslint-disable-next-line import/no-default-export export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; @@ -137,7 +135,7 @@ export default function (providerContext: FtrProviderContext) { }; describe('GET /internal/cloud_security_posture/stats', () => { - describe('cspm benchmarks', async () => { + describe('CSPM Compliance Dashboard Stats API', async () => { beforeEach(async () => { await index.removeFindings(); await index.removeScores(); @@ -147,25 +145,42 @@ export default function (providerContext: FtrProviderContext) { await index.addFindings([findingsMockData[1]]); }); - it('should return CSPM benchmarks ', async () => { + it('should return CSPM cluster V1 ', async () => { const { body: res }: { body: ComplianceDashboardData } = await kibanaHttpClient .get(`/internal/cloud_security_posture/stats/cspm`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - - const { resClusters, resBenchmarks, trends } = getNonTimestampResponseFields(res); + const resClusters = removeRealtimeClusterFields(res.clusters); + const trends = removeRealtimeCalculatedFields(res.trend); expect({ ...res, clusters: resClusters, + trend: trends, + }).to.eql(cspmComplianceDashboardDataMockV1); + }); + + it('should return CSPM benchmarks V2 ', async () => { + const { body: res }: { body: ComplianceDashboardDataV2 } = await kibanaHttpClient + .get(`/internal/cloud_security_posture/stats/cspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set('kbn-xsrf', 'xxxx') + .expect(200); + + const resBenchmarks = removeRealtimeBenchmarkFields(res.benchmarks); + + const trends = removeRealtimeCalculatedFields(res.trend); + + expect({ + ...res, benchmarks: resBenchmarks, trend: trends, - }).to.eql(cspmComplianceDashboardDataMock); + }).to.eql(cspmComplianceDashboardDataMockV2); }); }); - describe('kspm benchmarks', async () => { + describe('KSPM Compliance Dashboard Stats API', async () => { beforeEach(async () => { await index.removeFindings(); await index.removeScores(); @@ -175,21 +190,39 @@ export default function (providerContext: FtrProviderContext) { await index.addFindings([findingsMockData[0]]); }); - it('should return KSPM benchmarks ', async () => { + it('should return KSPM clusters V1 ', async () => { const { body: res }: { body: ComplianceDashboardData } = await kibanaHttpClient .get(`/internal/cloud_security_posture/stats/kspm`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set('kbn-xsrf', 'xxxx') .expect(200); - const { resClusters, resBenchmarks, trends } = getNonTimestampResponseFields(res); + const resClusters = removeRealtimeClusterFields(res.clusters); + const trends = removeRealtimeCalculatedFields(res.trend); expect({ ...res, clusters: resClusters, + trend: trends, + }).to.eql(kspmComplianceDashboardDataMockV1); + }); + + it('should return KSPM benchmarks V2 ', async () => { + const { body: res }: { body: ComplianceDashboardDataV2 } = await kibanaHttpClient + .get(`/internal/cloud_security_posture/stats/kspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set('kbn-xsrf', 'xxxx') + .expect(200); + + const resBenchmarks = removeRealtimeBenchmarkFields(res.benchmarks); + + const trends = removeRealtimeCalculatedFields(res.trend); + + expect({ + ...res, benchmarks: resBenchmarks, trend: trends, - }).to.eql(kspmComplianceDashboardDataMock); + }).to.eql(kspmComplianceDashboardDataMockV2); }); }); }); From 1ce83b73aaae187757d45caced264e3ae58ff083 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Wed, 29 Nov 2023 12:12:02 -0500 Subject: [PATCH 12/17] add dashboard ui with benchmarks --- .../public/common/api/use_stats_api.ts | 12 +- .../public/common/constants.ts | 2 + .../components/accounts_evaluated_widget.tsx | 16 +- .../public/components/chart_panel.tsx | 4 +- .../compliance_score_chart.tsx | 150 ++++++++++++--- .../compliance_dashboard.test.tsx | 26 +-- .../compliance_dashboard.tsx | 12 +- .../benchmark_details_box.tsx | 176 ++++++++++++++++++ .../benchmarks_section.test.tsx | 28 +-- .../dashboard_sections/benchmarks_section.tsx | 131 ++++++------- .../cluster_details_box.tsx | 125 ------------- .../dashboard_sections/summary_section.tsx | 34 ++-- .../public/pages/compliance_dashboard/mock.ts | 39 ++-- 13 files changed, 454 insertions(+), 301 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx delete mode 100644 x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts index 68ab9dfc698f7..834a75581519f 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts @@ -7,7 +7,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import { useKibana } from '../hooks/use_kibana'; -import { ComplianceDashboardData, PosturePolicyTemplate } from '../../../common/types'; +import { ComplianceDashboardDataV2, PosturePolicyTemplate } from '../../../common/types'; import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE, @@ -23,23 +23,25 @@ export const getStatsRoute = (policyTemplate: PosturePolicyTemplate) => { }; export const useCspmStatsApi = ( - options: UseQueryOptions + options: UseQueryOptions ) => { const { http } = useKibana().services; return useQuery( getCspmStatsKey, - () => http.get(getStatsRoute(CSPM_POLICY_TEMPLATE), { version: '1' }), + () => + http.get(getStatsRoute(CSPM_POLICY_TEMPLATE), { version: '2' }), options ); }; export const useKspmStatsApi = ( - options: UseQueryOptions + options: UseQueryOptions ) => { const { http } = useKibana().services; return useQuery( getKspmStatsKey, - () => http.get(getStatsRoute(KSPM_POLICY_TEMPLATE), { version: '1' }), + () => + http.get(getStatsRoute(KSPM_POLICY_TEMPLATE), { version: '2' }), options ); }; 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 7641745b897f4..5ea356e4a3836 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -45,6 +45,8 @@ export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pag export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; export const LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY = 'cloudPosture:complianceDashboard:clusterSort'; +export const LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY = + 'cloudPosture:complianceDashboard:benchmarkSort'; export const LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY = 'cloudPosture:findings:lastSelectedTab'; export type CloudPostureIntegrations = Record< diff --git a/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx b/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx index 418b1c37a1bdd..4feee3a5e2287 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/accounts_evaluated_widget.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { CIS_AWS, CIS_GCP, CIS_AZURE, CIS_K8S, CIS_EKS } from '../../common/constants'; -import { Cluster } from '../../common/types'; import { CISBenchmarkIcon } from './cis_benchmark_icon'; import { CompactFormattedNumber } from './compact_formatted_number'; import { useNavigateFindings } from '../common/hooks/use_navigate_findings'; +import { BenchmarkData } from '../../common/types'; // order in array will determine order of appearance in the dashboard const benchmarks = [ @@ -43,17 +43,17 @@ const benchmarks = [ ]; export const AccountsEvaluatedWidget = ({ - clusters, + benchmarkAssets, benchmarkAbbreviateAbove = 999, }: { - clusters: Cluster[]; + benchmarkAssets: BenchmarkData[]; /** numbers higher than the value of this field will be abbreviated using compact notation and have a tooltip displaying the full value */ benchmarkAbbreviateAbove?: number; }) => { const { euiTheme } = useEuiTheme(); - const filterClustersById = (benchmarkId: string) => { - return clusters?.filter((obj) => obj?.meta.benchmark.id === benchmarkId) || []; + const filterBenchmarksById = (benchmarkId: string) => { + return benchmarkAssets?.filter((obj) => obj?.meta.benchmarkId === benchmarkId) || []; }; const navToFindings = useNavigateFindings(); @@ -67,10 +67,10 @@ export const AccountsEvaluatedWidget = ({ }; const benchmarkElements = benchmarks.map((benchmark) => { - const clusterAmount = filterClustersById(benchmark.type).length; + const cloudAssetAmount = filterBenchmarksById(benchmark.type).length; return ( - clusterAmount > 0 && ( + cloudAssetAmount > 0 && ( { @@ -98,7 +98,7 @@ export const AccountsEvaluatedWidget = ({ diff --git a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx index b6ffc9f0157b8..6c813c480ed8c 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx @@ -23,6 +23,7 @@ interface ChartPanelProps { isLoading?: boolean; isError?: boolean; rightSideItems?: ReactNode[]; + styles?: React.CSSProperties; } const Loading = () => ( @@ -54,6 +55,7 @@ export const ChartPanel: React.FC = ({ isError, children, rightSideItems, + styles, }) => { const { euiTheme } = useEuiTheme(); const renderChart = () => { @@ -63,7 +65,7 @@ export const ChartPanel: React.FC = ({ }; return ( - + diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx index 956f63ed2d3bd..2067a9c98fd23 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx @@ -30,6 +30,7 @@ import { import { FormattedDate, FormattedTime } from '@kbn/i18n-react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; import { DASHBOARD_COMPLIANCE_SCORE_CHART } from '../test_subjects'; import { statusColors } from '../../../common/constants'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; @@ -45,6 +46,123 @@ interface ComplianceScoreChartProps { onEvalCounterClick: (evaluation: Evaluation) => void; } +const CounterButtonLink = ({ + text, + count, + color, + onClick, +}: { + count: number; + text: string; + color: EuiTextProps['color']; + onClick: EuiLinkButtonProps['onClick']; +}) => { + const { euiTheme } = useEuiTheme(); + + return ( + <> + + {text} + + + + + +   + + + + ); +}; + +const CompactPercentageLabels = ({ + onEvalCounterClick, + stats, +}: { + onEvalCounterClick: (evaluation: Evaluation) => void; + stats: { totalPassed: number; totalFailed: number }; +}) => ( + <> + onEvalCounterClick(RULE_PASSED)} + tooltipContent={i18n.translate( + 'xpack.csp.complianceScoreChart.counterLink.passedFindingsTooltip', + { defaultMessage: 'Passed findings' } + )} + /> +  -  + onEvalCounterClick(RULE_FAILED)} + tooltipContent={i18n.translate( + 'xpack.csp.complianceScoreChart.counterButtonLink.failedFindingsTooltip', + { defaultMessage: 'Failed findings' } + )} + /> + +); + +const NonCompactPercentageLabels = ({ + onEvalCounterClick, + stats, +}: { + onEvalCounterClick: (evaluation: Evaluation) => void; + stats: { totalPassed: number; totalFailed: number }; +}) => { + const { euiTheme } = useEuiTheme(); + const borderLeftStyles = { borderLeft: euiTheme.border.thin, paddingLeft: euiTheme.size.m }; + return ( + + + onEvalCounterClick(RULE_PASSED)} + /> + + + onEvalCounterClick(RULE_FAILED)} + /> + + + ); +}; + const getPostureScorePercentage = (postureScore: number): string => `${Math.round(postureScore)}%`; const PercentageInfo = ({ @@ -177,27 +295,17 @@ export const ComplianceScoreChart = ({ alignItems="flexStart" style={{ paddingRight: euiTheme.size.xl }} > - onEvalCounterClick(RULE_PASSED)} - tooltipContent={i18n.translate( - 'xpack.csp.complianceScoreChart.counterLink.passedFindingsTooltip', - { defaultMessage: 'Passed findings' } - )} - /> -  -  - onEvalCounterClick(RULE_FAILED)} - tooltipContent={i18n.translate( - 'xpack.csp.complianceScoreChart.counterLink.failedFindingsTooltip', - { defaultMessage: 'Failed findings' } - )} - /> + {compact ? ( + + ) : ( + + )} 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 1c8da9db27871..18e5118f772e5 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 @@ -32,7 +32,11 @@ import { KSPM_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT, PACKAGE_NOT_INSTALLED_TEST_SUBJECT, } from '../../components/cloud_posture_page'; -import { BaseCspSetupStatus, ComplianceDashboardData, CspStatusCode } from '../../../common/types'; +import { + BaseCspSetupStatus, + ComplianceDashboardDataV2, + CspStatusCode, +} from '../../../common/types'; jest.mock('../../common/api/use_setup_status_api'); jest.mock('../../common/api/use_stats_api'); @@ -779,31 +783,31 @@ describe('getDefaultTab', () => { it('returns CSPM tab if only CSPM has findings', () => { const pluginStatus = getPluginStatusMock('indexed', 'indexed') as BaseCspSetupStatus; - const cspmStats = getStatsMock(1) as ComplianceDashboardData; - const kspmStats = getStatsMock(0) as ComplianceDashboardData; + const cspmStats = getStatsMock(1) as ComplianceDashboardDataV2; + const kspmStats = getStatsMock(0) as ComplianceDashboardDataV2; expect(getDefaultTab(pluginStatus, cspmStats, kspmStats)).toEqual('cspm'); }); it('returns CSPM tab if both CSPM and KSPM has findings', () => { const pluginStatus = getPluginStatusMock('indexed', 'indexed') as BaseCspSetupStatus; - const cspmStats = getStatsMock(1) as ComplianceDashboardData; - const kspmStats = getStatsMock(1) as ComplianceDashboardData; + const cspmStats = getStatsMock(1) as ComplianceDashboardDataV2; + const kspmStats = getStatsMock(1) as ComplianceDashboardDataV2; expect(getDefaultTab(pluginStatus, cspmStats, kspmStats)).toEqual('cspm'); }); it('returns KSPM tab if only KSPM has findings', () => { const pluginStatus = getPluginStatusMock('indexed', 'indexed') as BaseCspSetupStatus; - const cspmStats = getStatsMock(0) as ComplianceDashboardData; - const kspmStats = getStatsMock(1) as ComplianceDashboardData; + const cspmStats = getStatsMock(0) as ComplianceDashboardDataV2; + const kspmStats = getStatsMock(1) as ComplianceDashboardDataV2; expect(getDefaultTab(pluginStatus, cspmStats, kspmStats)).toEqual('kspm'); }); it('when no findings preffers CSPM tab unless not-installed or unprivileged', () => { - const cspmStats = getStatsMock(0) as ComplianceDashboardData; - const kspmStats = getStatsMock(0) as ComplianceDashboardData; + const cspmStats = getStatsMock(0) as ComplianceDashboardDataV2; + const kspmStats = getStatsMock(0) as ComplianceDashboardDataV2; const CspStatusCodeArray: CspStatusCode[] = [ 'indexed', 'indexing', @@ -833,13 +837,13 @@ describe('getDefaultTab', () => { }); it('returns CSPM tab is plugin status and kspm status is not provided', () => { - const cspmStats = getStatsMock(1) as ComplianceDashboardData; + const cspmStats = getStatsMock(1) as ComplianceDashboardDataV2; expect(getDefaultTab(undefined, cspmStats, undefined)).toEqual('cspm'); }); it('returns KSPM tab is plugin status and csp status is not provided', () => { - const kspmStats = getStatsMock(1) as ComplianceDashboardData; + const kspmStats = getStatsMock(1) as ComplianceDashboardDataV2; expect(getDefaultTab(undefined, undefined, kspmStats)).toEqual('kspm'); }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx index 8b7b0d5c841af..9e3b8df0c1d3e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.tsx @@ -15,7 +15,7 @@ import { NO_FINDINGS_STATUS_TEST_SUBJ } from '../../components/test_subjects'; import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link'; import type { PosturePolicyTemplate, - ComplianceDashboardData, + ComplianceDashboardDataV2, BaseCspSetupStatus, } from '../../../common/types'; import { CloudPosturePageTitle } from '../../components/cloud_posture_page_title'; @@ -127,7 +127,7 @@ const IntegrationPostureDashboard = ({ isIntegrationInstalled, dashboardType, }: { - complianceData: ComplianceDashboardData | undefined; + complianceData: ComplianceDashboardDataV2 | undefined; notInstalledConfig: CspNoDataPageProps; isIntegrationInstalled?: boolean; dashboardType: PosturePolicyTemplate; @@ -188,8 +188,8 @@ const IntegrationPostureDashboard = ({ export const getDefaultTab = ( pluginStatus?: BaseCspSetupStatus, - cspmStats?: ComplianceDashboardData, - kspmStats?: ComplianceDashboardData + cspmStats?: ComplianceDashboardDataV2, + kspmStats?: ComplianceDashboardDataV2 ) => { const cspmTotalFindings = cspmStats?.stats.totalFindings; const kspmTotalFindings = kspmStats?.stats.totalFindings; @@ -223,7 +223,7 @@ export const getDefaultTab = ( return preferredDashboard; }; -const determineDashboardDataRefetchInterval = (data: ComplianceDashboardData | undefined) => { +const determineDashboardDataRefetchInterval = (data: ComplianceDashboardDataV2 | undefined) => { if (data?.stats.totalFindings === 0) { return NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS; } @@ -258,7 +258,7 @@ const TabContent = ({ posturetype }: { posturetype: PosturePolicyTemplate }) => let integrationLink; let dataTestSubj; let policyTemplate: PosturePolicyTemplate; - let getDashboardData: UseQueryResult; + let getDashboardData: UseQueryResult; switch (posturetype) { case POSTURE_TYPE_CSPM: diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx new file mode 100644 index 0000000000000..de982b50f48f6 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx @@ -0,0 +1,176 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiText, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { getBenchmarkIdQuery } from './benchmarks_section'; +import { BenchmarkData } from '../../../../common/types'; +import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings'; +import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon'; +import cisLogoIcon from '../../../assets/icons/cis_logo.svg'; +export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData }) => { + const navToFindings = useNavigateFindings(); + + const handleBenchmarkClick = () => { + return navToFindings(getBenchmarkIdQuery(benchmark)); + }; + + const getBenchmarkInfo = ( + benchmarkId: string, + cloudAssetCount: number + ): { name: string; assetType: string } => { + const benchmarks: Record = { + cis_gcp: { + name: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisGcpBenchmarkName', + { + defaultMessage: 'CIS GCP', + } + ), + assetType: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisGcpBenchmarkAssetType', + { + defaultMessage: '{count, plural, one {# Project} other {# Projects}}', + values: { count: cloudAssetCount }, + } + ), + }, + cis_aws: { + name: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisAwsBenchmarkName', + { + defaultMessage: 'CIS AWS', + } + ), + assetType: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisAwsBenchmarkAssetType', + { + defaultMessage: '{count, plural, one {# Account} other {# Accounts}}', + values: { count: cloudAssetCount }, + } + ), + }, + cis_azure: { + name: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisAzureBenchmarkName', + { + defaultMessage: 'CIS Azure', + } + ), + assetType: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisAzureBenchmarkAssetType', + { + defaultMessage: '{count, plural, one {# Subscription} other {# Subscriptions}}', + values: { count: cloudAssetCount }, + } + ), + }, + cis_k8s: { + name: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisK8sBenchmarkName', + { + defaultMessage: 'CIS Kubernetes', + } + ), + assetType: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisK8sBenchmarkAssetType', + { + defaultMessage: '{count, plural, one {# Cluster} other {# Clusters}}', + values: { count: cloudAssetCount }, + } + ), + }, + cis_eks: { + name: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisEksBenchmarkName', + { + defaultMessage: 'CIS EKS', + } + ), + assetType: i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisEksBenchmarkAssetType', + { + defaultMessage: '{count, plural, one {# Cluster} other {# Clusters}}', + values: { count: cloudAssetCount }, + } + ), + }, + }; + return benchmarks[benchmarkId]; + }; + + const cisTooltip = i18n.translate( + 'xpack.csp.dashboard.benchmarkSection.benchmarkName.cisBenchmarkTooltip', + { + defaultMessage: 'Center of Internet Security', + } + ); + + const benchmarkInfo = getBenchmarkInfo(benchmark.meta.benchmarkId, benchmark.meta.assetCount); + + const benchmarkId = benchmark.meta.benchmarkId; + const benchmarkVersion = benchmark.meta.benchmarkVersion; + const benchmarkName = benchmark.meta.benchmarkName; + + return ( + + + + + + + + + } + > + + +
{benchmarkInfo.name}
+
+
+
+ + + {benchmarkInfo.assetType} + +
+ + + + + + + + {benchmarkVersion} + + + +
+ ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.test.tsx index 5293eec114fc6..79b644af37795 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BenchmarksSection } from './benchmarks_section'; -import { getMockDashboardData, getClusterMockData } from '../mock'; +import { getMockDashboardData, getBenchmarkMockData } from '../mock'; import { TestProvider } from '../../../test/test_provider'; import { KSPM_POLICY_TEMPLATE } from '../../../../common/constants'; import { @@ -30,22 +30,22 @@ describe('', () => { describe('Sorting', () => { const mockDashboardDataCopy = getMockDashboardData(); - const clusterMockDataCopy = getClusterMockData(); - clusterMockDataCopy.stats.postureScore = 50; - clusterMockDataCopy.meta.assetIdentifierId = '1'; + const benchmarkMockDataCopy = getBenchmarkMockData(); + benchmarkMockDataCopy.stats.postureScore = 50; + benchmarkMockDataCopy.meta.benchmarkId = 'cis_aws'; - const clusterMockDataCopy1 = getClusterMockData(); - clusterMockDataCopy1.stats.postureScore = 95; - clusterMockDataCopy1.meta.assetIdentifierId = '2'; + const benchmarkMockDataCopy1 = getBenchmarkMockData(); + benchmarkMockDataCopy1.stats.postureScore = 95; + benchmarkMockDataCopy1.meta.benchmarkId = 'cis_azure'; - const clusterMockDataCopy2 = getClusterMockData(); - clusterMockDataCopy2.stats.postureScore = 45; - clusterMockDataCopy2.meta.assetIdentifierId = '3'; + const benchmarkMockDataCopy2 = getBenchmarkMockData(); + benchmarkMockDataCopy2.stats.postureScore = 45; + benchmarkMockDataCopy2.meta.benchmarkId = 'cis_gcp'; - mockDashboardDataCopy.clusters = [ - clusterMockDataCopy, - clusterMockDataCopy1, - clusterMockDataCopy2, + mockDashboardDataCopy.benchmarks = [ + benchmarkMockDataCopy, + benchmarkMockDataCopy1, + benchmarkMockDataCopy2, ]; it('sorts by ascending order of compliance scores', () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx index 96da28e0c8012..9869127af9180 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx @@ -7,97 +7,97 @@ import React, { useMemo } from 'react'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import type { EuiIconProps } from '@elastic/eui'; +import { EuiIconProps, EuiPanel } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import type { - Cluster, - ComplianceDashboardData, + BenchmarkData, + ComplianceDashboardDataV2, Evaluation, PosturePolicyTemplate, } from '../../../../common/types'; -import { LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY } from '../../../common/constants'; import { RisksTable } from '../compliance_charts/risks_table'; -import { - CSPM_POLICY_TEMPLATE, - KSPM_POLICY_TEMPLATE, - RULE_FAILED, -} from '../../../../common/constants'; +import { RULE_FAILED } from '../../../../common/constants'; +import { LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY } from '../../../common/constants'; import { NavFilter, useNavigateFindings } from '../../../common/hooks/use_navigate_findings'; -import { ClusterDetailsBox } from './cluster_details_box'; import { dashboardColumnsGrow, getPolicyTemplateQuery } from './summary_section'; import { DASHBOARD_TABLE_COLUMN_SCORE_TEST_ID, DASHBOARD_TABLE_HEADER_SCORE_TEST_ID, } from '../test_subjects'; import { ComplianceScoreChart } from '../compliance_charts/compliance_score_chart'; +import { BenchmarkDetailsBox } from './benchmark_details_box'; +const BENCHMARK_DEFAULT_SORT_ORDER = 'asc'; -const CLUSTER_DEFAULT_SORT_ORDER = 'asc'; - -export const getClusterIdQuery = (cluster: Cluster): NavFilter => { - if (cluster.meta.benchmark.posture_type === CSPM_POLICY_TEMPLATE) { - // TODO: remove assertion after typing CspFinding as discriminating union - return { 'cloud.account.id': cluster.meta.cloud!.account.id }; - } - return { cluster_id: cluster.meta.assetIdentifierId }; +export const getBenchmarkIdQuery = (benchmark: BenchmarkData): NavFilter => { + return { + 'rule.benchmark.id': benchmark.meta.benchmarkId, + 'rule.benchmark.version': benchmark.meta.benchmarkVersion, + }; }; export const BenchmarksSection = ({ complianceData, dashboardType, }: { - complianceData: ComplianceDashboardData; + complianceData: ComplianceDashboardDataV2; dashboardType: PosturePolicyTemplate; }) => { const { euiTheme } = useEuiTheme(); const navToFindings = useNavigateFindings(); - const [clusterSorting, setClusterSorting] = useLocalStorage<'asc' | 'desc'>( - LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY, - CLUSTER_DEFAULT_SORT_ORDER + const [benchmarkSorting, setBenchmarkSorting] = useLocalStorage<'asc' | 'desc'>( + LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY, + BENCHMARK_DEFAULT_SORT_ORDER ); - const isClusterSortingAsc = clusterSorting === 'asc'; + const isBenchmarkSortingAsc = benchmarkSorting === 'asc'; - const clusterSortingIcon: EuiIconProps['type'] = isClusterSortingAsc ? 'sortUp' : 'sortDown'; + const benchmarkSortingIcon: EuiIconProps['type'] = isBenchmarkSortingAsc ? 'sortUp' : 'sortDown'; - const navToFindingsByClusterAndEvaluation = (cluster: Cluster, evaluation: Evaluation) => { + const navToFindingsByBenchmarkAndEvaluation = ( + benchmark: BenchmarkData, + evaluation: Evaluation + ) => { navToFindings({ ...getPolicyTemplateQuery(dashboardType), - ...getClusterIdQuery(cluster), + ...getBenchmarkIdQuery(benchmark), 'result.evaluation': evaluation, }); }; - const navToFailedFindingsByClusterAndSection = (cluster: Cluster, ruleSection: string) => { + const navToFailedFindingsByBenchmarkAndSection = ( + benchmark: BenchmarkData, + ruleSection: string + ) => { navToFindings({ ...getPolicyTemplateQuery(dashboardType), - ...getClusterIdQuery(cluster), + ...getBenchmarkIdQuery(benchmark), 'rule.section': ruleSection, 'result.evaluation': RULE_FAILED, }); }; - const navToFailedFindingsByCluster = (cluster: Cluster) => { - navToFindingsByClusterAndEvaluation(cluster, RULE_FAILED); + const navToFailedFindingsByBenchmark = (benchmark: BenchmarkData) => { + navToFindingsByBenchmarkAndEvaluation(benchmark, RULE_FAILED); }; - const toggleClustersSortingDirection = () => { - setClusterSorting(isClusterSortingAsc ? 'desc' : 'asc'); + const toggleBenchmarkSortingDirection = () => { + setBenchmarkSorting(isBenchmarkSortingAsc ? 'desc' : 'asc'); }; - const clusters = useMemo(() => { - return [...complianceData.clusters].sort((clusterA, clusterB) => - isClusterSortingAsc - ? clusterA.stats.postureScore - clusterB.stats.postureScore - : clusterB.stats.postureScore - clusterA.stats.postureScore + const benchmarks = useMemo(() => { + return [...complianceData.benchmarks].sort((benchmarkA, benchmarkB) => + isBenchmarkSortingAsc + ? benchmarkA.stats.postureScore - benchmarkB.stats.postureScore + : benchmarkB.stats.postureScore - benchmarkA.stats.postureScore ); - }, [complianceData.clusters, isClusterSortingAsc]); + }, [complianceData.benchmarks, isBenchmarkSortingAsc]); return ( - <> +
- {dashboardType === KSPM_POLICY_TEMPLATE ? ( - - ) : ( - - )} +
@@ -155,9 +148,9 @@ export const BenchmarksSection = ({
- {clusters.map((cluster) => ( + {benchmarks.map((benchmark) => ( - + - navToFindingsByClusterAndEvaluation(cluster, evaluation) + navToFindingsByBenchmarkAndEvaluation(benchmark, evaluation) } /> @@ -193,27 +186,23 @@ export const BenchmarksSection = ({ > - navToFailedFindingsByClusterAndSection(cluster, resourceTypeName) + navToFailedFindingsByBenchmarkAndSection(benchmark, resourceTypeName) } viewAllButtonTitle={i18n.translate( - 'xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle', + 'xpack.csp.dashboard.risksTable.benchmarkCardViewAllButtonTitle', { - defaultMessage: 'View all failed findings for this {postureAsset}', - values: { - postureAsset: - dashboardType === CSPM_POLICY_TEMPLATE ? 'cloud account' : 'cluster', - }, + defaultMessage: 'View all failed findings for this benchmark', } )} - onViewAllClick={() => navToFailedFindingsByCluster(cluster)} + onViewAllClick={() => navToFailedFindingsByBenchmark(benchmark)} /> ))} - +
); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx deleted file mode 100644 index 7b42445d26b99..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/cluster_details_box.tsx +++ /dev/null @@ -1,125 +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 { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiText, - EuiTitle, - EuiToolTip, - useEuiTheme, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import moment from 'moment'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { getClusterIdQuery } from './benchmarks_section'; -import { CSPM_POLICY_TEMPLATE, INTERNAL_FEATURE_FLAGS } from '../../../../common/constants'; -import { Cluster } from '../../../../common/types'; -import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings'; -import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon'; - -const defaultClusterTitle = i18n.translate( - 'xpack.csp.dashboard.benchmarkSection.defaultClusterTitle', - { defaultMessage: 'ID' } -); - -const getClusterTitle = (cluster: Cluster) => { - if (cluster.meta.benchmark.posture_type === CSPM_POLICY_TEMPLATE) { - return cluster.meta.cloud?.account.name; - } - - return cluster.meta.cluster?.name; -}; - -const getClusterId = (cluster: Cluster) => { - const assetIdentifierId = cluster.meta.assetIdentifierId; - if (cluster.meta.benchmark.posture_type === CSPM_POLICY_TEMPLATE) return assetIdentifierId; - return assetIdentifierId.slice(0, 6); -}; - -export const ClusterDetailsBox = ({ cluster }: { cluster: Cluster }) => { - const { euiTheme } = useEuiTheme(); - const navToFindings = useNavigateFindings(); - - const assetId = getClusterId(cluster); - const title = getClusterTitle(cluster) || defaultClusterTitle; - - const handleClusterTitleClick = () => { - return navToFindings(getClusterIdQuery(cluster)); - }; - - return ( - - - - - - - - - } - > - - -
- -
-
-
-
- - - -
- - - - {INTERNAL_FEATURE_FLAGS.showManageRulesMock && ( - - - - - - )} -
- ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index a0170705b1ec5..0f2061f8f3bd1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -6,7 +6,13 @@ */ import React, { useMemo } from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFlexItemProps } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlexItemProps, + useEuiTheme, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { useCspIntegrationLink } from '../../../common/navigation/use_csp_integration_link'; @@ -16,7 +22,7 @@ import { CompactFormattedNumber } from '../../../components/compact_formatted_nu import { ChartPanel } from '../../../components/chart_panel'; import { ComplianceScoreChart } from '../compliance_charts/compliance_score_chart'; import type { - ComplianceDashboardData, + ComplianceDashboardDataV2, Evaluation, PosturePolicyTemplate, } from '../../../../common/types'; @@ -48,12 +54,14 @@ export const SummarySection = ({ complianceData, }: { dashboardType: PosturePolicyTemplate; - complianceData: ComplianceDashboardData; + complianceData: ComplianceDashboardDataV2; }) => { const navToFindings = useNavigateFindings(); const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE); const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE); + const { euiTheme } = useEuiTheme(); + const handleEvalCounterClick = (evaluation: Evaluation) => { navToFindings({ 'result.evaluation': evaluation, ...getPolicyTemplateQuery(dashboardType) }); }; @@ -84,7 +92,7 @@ export const SummarySection = ({ 'xpack.csp.dashboard.summarySection.counterCard.accountsEvaluatedDescription', { defaultMessage: 'Accounts Evaluated' } ), - title: , + title: , button: ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/mock.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/mock.ts index 19040892e7e67..b4a8eae816b24 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/mock.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/mock.ts @@ -5,29 +5,18 @@ * 2.0. */ -import { Cluster, ComplianceDashboardData } from '../../../common/types'; +import { BenchmarkData, ComplianceDashboardDataV2 } from '../../../common/types'; -export const getClusterMockData = (): Cluster => ({ +export const getMockDashboardData = () => ({ + ...mockDashboardData, +}); + +export const getBenchmarkMockData = (): BenchmarkData => ({ meta: { - assetIdentifierId: '8f9c5b98-cc02-4827-8c82-316e2cc25870', - lastUpdate: '2022-11-07T13:14:34.990Z', - cloud: { - provider: 'aws', - account: { - name: 'build-security-dev', - id: '704479110758', - }, - }, - benchmark: { - name: 'CIS Amazon Web Services Foundations', - rule_number: '1.4', - id: 'cis_aws', - posture_type: 'cspm', - version: 'v1.5.0', - }, - cluster: { - name: '8f9c5b98-cc02-4827-8c82-316e2cc25870', - }, + benchmarkId: 'cis_aws', + benchmarkVersion: '1.2.3', + benchmarkName: 'CIS AWS Foundations Benchmark', + assetCount: 153, }, stats: { totalFailed: 17, @@ -104,11 +93,7 @@ export const getClusterMockData = (): Cluster => ({ ], }); -export const getMockDashboardData = () => ({ - ...mockDashboardData, -}); - -export const mockDashboardData: ComplianceDashboardData = { +export const mockDashboardData: ComplianceDashboardDataV2 = { stats: { totalFailed: 17, totalPassed: 155, @@ -167,7 +152,7 @@ export const mockDashboardData: ComplianceDashboardData = { postureScore: 50.0, }, ], - clusters: [getClusterMockData()], + benchmarks: [getBenchmarkMockData()], trend: [ { timestamp: '2022-05-22T11:03:00.000Z', From 92394f7343ae74f8b45a7bc2e23feb03a39e64b4 Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Wed, 29 Nov 2023 13:56:05 -0500 Subject: [PATCH 13/17] fix border styles --- .../dashboard_sections/benchmarks_section.tsx | 10 ++++++---- .../dashboard_sections/summary_section.tsx | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx index 9869127af9180..2ac91288475de 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx @@ -100,9 +100,9 @@ export const BenchmarksSection = ({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index 0f2061f8f3bd1..5839dde7ea1a9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -188,6 +188,9 @@ export const SummarySection = ({ 'xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle', { defaultMessage: 'Compliance By CIS Section' } )} + styles={{ + padding: `${euiTheme.size.m} ${euiTheme.size.m} ${euiTheme.size.s} ${euiTheme.size.m}`, + }} > Date: Wed, 29 Nov 2023 14:17:48 -0500 Subject: [PATCH 14/17] fix extra padding --- .../cloud_security_posture/public/components/chart_panel.tsx | 2 -- .../dashboard_sections/summary_section.tsx | 3 --- 2 files changed, 5 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx index 6c813c480ed8c..027e802c61935 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx @@ -23,7 +23,6 @@ interface ChartPanelProps { isLoading?: boolean; isError?: boolean; rightSideItems?: ReactNode[]; - styles?: React.CSSProperties; } const Loading = () => ( @@ -55,7 +54,6 @@ export const ChartPanel: React.FC = ({ isError, children, rightSideItems, - styles, }) => { const { euiTheme } = useEuiTheme(); const renderChart = () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index 5839dde7ea1a9..0f2061f8f3bd1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -188,9 +188,6 @@ export const SummarySection = ({ 'xpack.csp.dashboard.summarySection.complianceByCisSectionPanelTitle', { defaultMessage: 'Compliance By CIS Section' } )} - styles={{ - padding: `${euiTheme.size.m} ${euiTheme.size.m} ${euiTheme.size.s} ${euiTheme.size.m}`, - }} > Date: Wed, 29 Nov 2023 16:03:14 -0500 Subject: [PATCH 15/17] fix alignment issue --- .../public/components/chart_panel.tsx | 2 ++ .../dashboard_sections/summary_section.tsx | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx index 027e802c61935..6c813c480ed8c 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/chart_panel.tsx @@ -23,6 +23,7 @@ interface ChartPanelProps { isLoading?: boolean; isError?: boolean; rightSideItems?: ReactNode[]; + styles?: React.CSSProperties; } const Loading = () => ( @@ -54,6 +55,7 @@ export const ChartPanel: React.FC = ({ isError, children, rightSideItems, + styles, }) => { const { euiTheme } = useEuiTheme(); const renderChart = () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index 0f2061f8f3bd1..ca4a55c45ebdd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -159,9 +159,6 @@ export const SummarySection = ({ height: 310px; `} data-test-subj={DASHBOARD_SUMMARY_CONTAINER} - style={{ - padding: `${euiTheme.size.m} ${euiTheme.size.m} ${euiTheme.size.s} ${euiTheme.size.m}`, - }} > @@ -184,6 +181,9 @@ export const SummarySection = ({ Date: Thu, 30 Nov 2023 08:43:09 -0500 Subject: [PATCH 16/17] address pr comments --- .../server/lib/mapping_field_util.test.ts | 47 +++++++++++++++++++ .../server/lib/mapping_field_util.ts | 21 +++++---- .../compliance_dashboard.ts | 16 +++---- .../compliance_dashboard/get_benchmarks.ts | 8 ++-- .../compliance_dashboard/get_clusters.ts | 11 ++--- .../get_grouped_findings_evaluation.test.ts | 8 ++-- .../get_grouped_findings_evaluation.ts | 23 +++------ .../routes/compliance_dashboard/get_stats.ts | 4 +- .../routes/compliance_dashboard/get_trends.ts | 41 ++++++++-------- .../translations/translations/fr-FR.json | 12 ----- .../translations/translations/ja-JP.json | 12 ----- .../translations/translations/zh-CN.json | 12 ----- 12 files changed, 109 insertions(+), 106 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.test.ts diff --git a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.test.ts b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.test.ts new file mode 100644 index 0000000000000..848a7ac0aa399 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.test.ts @@ -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 { + toBenchmarkDocFieldKey, + toBenchmarkMappingFieldKey, + MAPPING_VERSION_DELIMITER, +} from './mapping_field_util'; // replace 'yourFile' with the actual file name + +describe('Benchmark Field Key Functions', () => { + const sampleBenchmarkId = 'cis_aws'; + const sampleBenchmarkVersion = '1.0.0'; + + it('toBenchmarkDocFieldKey should keep the same benchmark id and version key for benchmark document', () => { + const result = toBenchmarkDocFieldKey(sampleBenchmarkId, sampleBenchmarkVersion); + const expected = `${sampleBenchmarkId};${sampleBenchmarkVersion}`; + expect(result).toEqual(expected); + }); + + it('toBenchmarkDocFieldKey should convert benchmark version with . delimiter correctly', () => { + const benchmarkVersionWithDelimiter = '1_0_0'; + const result = toBenchmarkDocFieldKey(sampleBenchmarkId, benchmarkVersionWithDelimiter); + const expected = `${sampleBenchmarkId};1.0.0`; + expect(result).toEqual(expected); + }); + + it('toBenchmarkMappingFieldKey should convert benchmark version with _ delimiter correctly', () => { + const result = toBenchmarkMappingFieldKey(sampleBenchmarkVersion); + const expected = '1_0_0'; + expect(result).toEqual(expected); + }); + + it('toBenchmarkMappingFieldKey should handle benchmark version with dots correctly', () => { + const benchmarkVersionWithDots = '1.0.0'; + const result = toBenchmarkMappingFieldKey(benchmarkVersionWithDots); + const expected = '1_0_0'; + expect(result).toEqual(expected); + }); + + it('MAPPING_VERSION_DELIMITER should be an underscore', () => { + expect(MAPPING_VERSION_DELIMITER).toBe('_'); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts index aac9701cf082e..7cc392d3da748 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/mapping_field_util.ts @@ -7,12 +7,17 @@ export const MAPPING_VERSION_DELIMITER = '_'; -export const toBenchmarkDocFieldKey = (benchmarkId: string, benchmarkVersion: string) => { - if (benchmarkVersion.includes(MAPPING_VERSION_DELIMITER)) - return `${benchmarkId};${benchmarkVersion.replaceAll('_', '.')}`; - return `${benchmarkId};${benchmarkVersion}`; -}; +/* + * The latest finding index store benchmark version field value `v1.2.0` + * when we store the benchmark id and version field name in the benchmark scores index, + * we need benchmark version with _ delimiter to avoid JSON mapping for each dot notation + * to be read as key. e.g. `v1.2.0` will be `v1_2_0` + */ + +export const toBenchmarkDocFieldKey = (benchmarkId: string, benchmarkVersion: string) => + benchmarkVersion.includes(MAPPING_VERSION_DELIMITER) + ? `${benchmarkId};${benchmarkVersion.replaceAll('_', '.')}` + : `${benchmarkId};${benchmarkVersion}`; -export const toBenchmarkMappingFieldKey = (benchmarkVersion: string) => { - return `${benchmarkVersion.replaceAll('.', '_')}`; -}; +export const toBenchmarkMappingFieldKey = (benchmarkVersion: string) => + `${benchmarkVersion.replaceAll('.', '_')}`; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index be51ee10cacc0..88c7afd5aca11 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -100,10 +100,10 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] = await Promise.all([ - getStats(logger, esClient, query, pitId, runtimeMappings), - getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), - getClusters(logger, esClient, query, pitId, runtimeMappings), - getTrends(logger, esClient, policyTemplate), + getStats(esClient, query, pitId, runtimeMappings, logger), + getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings, logger), + getClusters(esClient, query, pitId, runtimeMappings, logger), + getTrends(esClient, policyTemplate, logger), ]); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive @@ -172,10 +172,10 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => const [stats, groupedFindingsEvaluation, benchmarksWithoutTrends, trends] = await Promise.all([ - getStats(logger, esClient, query, pitId, runtimeMappings), - getGroupedFindingsEvaluation(logger, esClient, query, pitId, runtimeMappings), - getBenchmarks(logger, esClient, query, pitId, runtimeMappings), - getTrends(logger, esClient, policyTemplate), + getStats(esClient, query, pitId, runtimeMappings, logger), + getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings, logger), + getBenchmarks(esClient, query, pitId, runtimeMappings, logger), + getTrends(esClient, policyTemplate, logger), ]); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts index 08efc0d994ca6..40bb076d7ca0a 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -17,7 +17,7 @@ import type { BenchmarkData } from '../../../common/types'; import { failedFindingsAggQuery, BenchmarkVersionQueryResult, - getFailedFindingsFromAggs, + getPostureStatsFromAggs, } from './get_grouped_findings_evaluation'; import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats'; import { KeyDocCount } from './compliance_dashboard'; @@ -108,7 +108,7 @@ export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { const resourcesTypesAggs = version.aggs_by_resource_type.buckets; if (!Array.isArray(resourcesTypesAggs)) throw new Error('missing aggs by resource type per benchmark'); - const groupedFindingsEvaluation = getFailedFindingsFromAggs(resourcesTypesAggs); + const groupedFindingsEvaluation = getPostureStatsFromAggs(resourcesTypesAggs); return { meta: { @@ -125,11 +125,11 @@ export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { }; export const getBenchmarks = async ( - logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, - runtimeMappings: MappingRuntimeFields + runtimeMappings: MappingRuntimeFields, + logger: Logger ): Promise => { try { const queryResult = await esClient.search( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts index 8fc13f69cc8fb..51d5c71673ed1 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts @@ -17,10 +17,7 @@ import type { Logger } from '@kbn/core/server'; import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import { CspFinding } from '../../../common/schemas/csp_finding'; import type { Cluster } from '../../../common/types'; -import { - getFailedFindingsFromAggs, - failedFindingsAggQuery, -} from './get_grouped_findings_evaluation'; +import { getPostureStatsFromAggs, failedFindingsAggQuery } from './get_grouped_findings_evaluation'; import type { FailedFindingsQueryResult } from './get_grouped_findings_evaluation'; import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats'; import { KeyDocCount } from './compliance_dashboard'; @@ -100,7 +97,7 @@ export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTr const resourcesTypesAggs = clusterBucket.aggs_by_resource_type.buckets; if (!Array.isArray(resourcesTypesAggs)) throw new Error('missing aggs by resource type per cluster'); - const groupedFindingsEvaluation = getFailedFindingsFromAggs(resourcesTypesAggs); + const groupedFindingsEvaluation = getPostureStatsFromAggs(resourcesTypesAggs); return { meta, @@ -110,11 +107,11 @@ export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTr }); export const getClusters = async ( - logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, - runtimeMappings: MappingRuntimeFields + runtimeMappings: MappingRuntimeFields, + logger: Logger ): Promise => { try { const queryResult = await esClient.search( 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 6af6d97f51e26..5ebc5231dee6a 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 @@ -5,9 +5,9 @@ * 2.0. */ -import { getFailedFindingsFromAggs, FailedFindingsBucket } from './get_grouped_findings_evaluation'; +import { getPostureStatsFromAggs, PostureStatsBucket } from './get_grouped_findings_evaluation'; -const resourceTypeBuckets: FailedFindingsBucket[] = [ +const resourceTypeBuckets: PostureStatsBucket[] = [ { key: 'foo_type', doc_count: 41, @@ -36,9 +36,9 @@ const resourceTypeBuckets: FailedFindingsBucket[] = [ }, ]; -describe('getFailedFindingsFromAggs', () => { +describe('getPostureStatsFromAggs', () => { it('should return value matching ComplianceDashboardData["resourcesTypes"]', async () => { - const resourceTypes = getFailedFindingsFromAggs(resourceTypeBuckets); + const resourceTypes = getPostureStatsFromAggs(resourceTypeBuckets); expect(resourceTypes).toEqual([ { name: 'foo_type', 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 4b8555dfd5404..74b239f14d242 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 @@ -18,7 +18,7 @@ import type { ComplianceDashboardData } from '../../../common/types'; import { KeyDocCount } from './compliance_dashboard'; export interface FailedFindingsQueryResult { - aggs_by_resource_type: Aggregation; + aggs_by_resource_type: Aggregation; } export interface BenchmarkVersionQueryResult extends KeyDocCount, FailedFindingsQueryResult { @@ -33,17 +33,8 @@ export interface BenchmarkVersionQueryResult extends KeyDocCount, FailedFindings }; aggs_by_benchmark_name: Aggregation; } -export interface FailedFindingsBucket extends KeyDocCount { - failed_findings: { - doc_count: number; - }; - passed_findings: { - doc_count: number; - }; - score: { value: number }; -} -export interface FailedFindingsBucket extends KeyDocCount { +export interface PostureStatsBucket extends KeyDocCount { failed_findings: { doc_count: number; }; @@ -102,8 +93,8 @@ export const getRisksEsQuery = ( }, }); -export const getFailedFindingsFromAggs = ( - queryResult: FailedFindingsBucket[] +export const getPostureStatsFromAggs = ( + queryResult: PostureStatsBucket[] ): ComplianceDashboardData['groupedFindingsEvaluation'] => queryResult.map((bucket) => { const totalPassed = bucket.passed_findings.doc_count || 0; @@ -119,11 +110,11 @@ export const getFailedFindingsFromAggs = ( }); export const getGroupedFindingsEvaluation = async ( - logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, - runtimeMappings: MappingRuntimeFields + runtimeMappings: MappingRuntimeFields, + logger: Logger ): Promise => { try { const resourceTypesQueryResult = await esClient.search( @@ -135,7 +126,7 @@ export const getGroupedFindingsEvaluation = async ( return []; } - return getFailedFindingsFromAggs(ruleSections); + return getPostureStatsFromAggs(ruleSections); } catch (err) { logger.error(`Failed to fetch findings stats ${err.message}`); logger.error(err); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts index a9bddfc6a506f..f639f8a7e1421 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_stats.ts @@ -79,11 +79,11 @@ export const getStatsFromFindingsEvaluationsAggs = ( }; export const getStats = async ( - logger: Logger, esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string, - runtimeMappings: MappingRuntimeFields + runtimeMappings: MappingRuntimeFields, + logger: Logger ): Promise => { try { const evaluationsQueryResult = await esClient.search( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts index a74ebdd99fa98..6ae25bf966552 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts @@ -11,30 +11,29 @@ import { BENCHMARK_SCORE_INDEX_DEFAULT_NS } from '../../../common/constants'; import type { PosturePolicyTemplate, Stats } from '../../../common/types'; import { toBenchmarkDocFieldKey } from '../../lib/mapping_field_util'; +interface FindingsDetails { + total_findings: number; + passed_findings: number; + failed_findings: number; +} + +interface ScoreByClusterId { + [clusterId: string]: FindingsDetails; +} + +interface ScoreByBenchmarkId { + [benchmarkId: string]: { + [key: string]: FindingsDetails; + }; +} + export interface ScoreTrendDoc { '@timestamp': string; total_findings: number; passed_findings: number; failed_findings: number; - score_by_cluster_id: Record< - string, - { - total_findings: number; - passed_findings: number; - failed_findings: number; - } - >; - score_by_benchmark_id: Record< - string, - Record< - string, - { - total_findings: number; - passed_findings: number; - failed_findings: number; - } - > - >; + score_by_cluster_id: ScoreByClusterId; + score_by_benchmark_id: ScoreByBenchmarkId; } export type Trends = Array<{ @@ -111,9 +110,9 @@ export const formatTrends = (scoreTrendDocs: ScoreTrendDoc[]): Trends => { }; export const getTrends = async ( - logger: Logger, esClient: ElasticsearchClient, - policyTemplate: PosturePolicyTemplate + policyTemplate: PosturePolicyTemplate, + logger: Logger ): Promise => { try { const trendsQueryResult = await esClient.search(getTrendsQuery(policyTemplate)); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index cb92fe0fe194d..ce4ebcd5b4c6b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12082,11 +12082,6 @@ "xpack.csp.cloudPosturePage.kspmIntegration.packageNotInstalled.description": "Utilisez notre intégration {integrationFullName} (KSPM) pour détecter les erreurs de configuration de sécurité dans vos clusters Kubernetes.", "xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptDescription": "Détectez et corrigez les risques de configuration potentiels dans votre infrastructure cloud, comme les compartiments S3 accessibles au public, avec nos solutions de gestion du niveau de sécurité Kubernetes et de gestion du niveau de sécurité du cloud. {learnMore}", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed} résultats en échec et {passed} ayant réussi", - "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "Dernière évaluation {dateFromNow}", - "xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle": "Afficher tous les échecs des résultats pour ce {postureAsset}", - "xpack.csp.dashboard.summarySection.postureScorePanelTitle": "Score de sécurité {type} global", "xpack.csp.eksIntegration.docsLink": "Lisez {docs} pour en savoir plus", "xpack.csp.findings..bottomBarLabel": "Voici les {maxItems} premiers résultats correspondant à votre recherche. Veuillez l'affiner pour en voir davantage.", "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "Affichage de {pageStart}-{pageEnd} sur {total} {type}", @@ -12203,7 +12198,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "Vulnérabilités", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "Vulnérabilités", "xpack.csp.compactFormattedNumber.naTitle": "S. O.", - "xpack.csp.complianceScoreChart.counterLink.failedFindingsTooltip": "Échec des résultats", "xpack.csp.complianceScoreChart.counterLink.passedFindingsTooltip": "Réussite des résultats", "xpack.csp.createDetectionRuleButton": "Créer une règle de détection", "xpack.csp.createPackagePolicy.customAssetsTab.cloudNativeVulnerabilityManagementTitleLabel": "Gestion des vulnérabilités natives du cloud ", @@ -12223,13 +12217,7 @@ "xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP", "xpack.csp.cspmIntegration.integration.nameTitle": "Gestion du niveau de sécurité du cloud", "xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "Afficher tous les résultats pour ", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.accountNameTitle": "Nom du compte", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.clusterNameTitle": "Nom du cluster", "xpack.csp.dashboard.benchmarkSection.columnsHeader.complianceByCisSectionTitle": "Conformité par section CIS", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.postureScoreTitle": "Score du niveau", - "xpack.csp.dashboard.benchmarkSection.defaultClusterTitle": "ID", - "xpack.csp.dashboard.benchmarkSection.manageRulesButton": "Gérer les règles", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "Niveau de sécurité du cloud", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "Section CIS", "xpack.csp.dashboard.risksTable.complianceColumnLabel": "Conformité", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6c0effafd281c..332bedb5f0d21 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12096,11 +12096,6 @@ "xpack.csp.cloudPosturePage.kspmIntegration.packageNotInstalled.description": "{integrationFullName}(CSPM)統合を使用して、Kubernetesクラスターの構成エラーを検出します。", "xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptDescription": "クラウドおよびKubernetesセキュリティ態勢管理ソリューションを利用して、クラウドインフラの構成リスク(誰でもアクセス可能なS3バケットなど)の可能性を検出し、修正します。{learnMore}", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed}が失敗し、{passed}が調査結果に合格しました", - "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "前回評価:{dateFromNow}", - "xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle": "この{postureAsset}の失敗した調査結果をすべて表示", - "xpack.csp.dashboard.summarySection.postureScorePanelTitle": "全体的な{type}態勢スコア", "xpack.csp.eksIntegration.docsLink": "詳細は{docs}をご覧ください", "xpack.csp.findings..bottomBarLabel": "これらは検索条件に一致した初めの{maxItems}件の調査結果です。他の結果を表示するには検索条件を絞ってください。", "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "{total} {type}ページ中{pageStart}-{pageEnd}ページを表示中", @@ -12217,7 +12212,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "脆弱性", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "脆弱性", "xpack.csp.compactFormattedNumber.naTitle": "N/A", - "xpack.csp.complianceScoreChart.counterLink.failedFindingsTooltip": "失敗した調査結果", "xpack.csp.complianceScoreChart.counterLink.passedFindingsTooltip": "合格した調査結果", "xpack.csp.createDetectionRuleButton": "検出ルールを作成", "xpack.csp.createPackagePolicy.customAssetsTab.cloudNativeVulnerabilityManagementTitleLabel": "Cloud Native Vulnerability Management ", @@ -12237,13 +12231,7 @@ "xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP", "xpack.csp.cspmIntegration.integration.nameTitle": "クラウドセキュリティ態勢管理", "xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "すべての調査結果を表示 ", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.accountNameTitle": "アカウント名", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.clusterNameTitle": "クラスター名", "xpack.csp.dashboard.benchmarkSection.columnsHeader.complianceByCisSectionTitle": "CISセクション別のコンプライアンス", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.postureScoreTitle": "態勢スコア", - "xpack.csp.dashboard.benchmarkSection.defaultClusterTitle": "ID", - "xpack.csp.dashboard.benchmarkSection.manageRulesButton": "ルールの管理", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "クラウドセキュリティ態勢", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CISセクション", "xpack.csp.dashboard.risksTable.complianceColumnLabel": "コンプライアンス", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b58c600756803..d6230bfad0295 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12096,11 +12096,6 @@ "xpack.csp.cloudPosturePage.kspmIntegration.packageNotInstalled.description": "使用我们的 {integrationFullName} (KSPM) 集成可在您的 Kubernetes 集群中检测安全配置错误。", "xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptDescription": "使用我们的云和 Kubernetes 安全态势管理解决方案,在您的云基础设施中检测并缓解潜在的配置风险,如可公开访问的 S3 存储桶。{learnMore}", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed} 个失败和 {passed} 个通过的结果", - "xpack.csp.dashboard.benchmarkSection.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterTitle": "{title} - {assetId}", - "xpack.csp.dashboard.benchmarkSection.lastEvaluatedTitle": "上次评估于 {dateFromNow}", - "xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle": "查看此 {postureAsset} 的所有失败结果", - "xpack.csp.dashboard.summarySection.postureScorePanelTitle": "总体 {type} 态势分数", "xpack.csp.eksIntegration.docsLink": "请参阅 {docs} 了解更多详情", "xpack.csp.findings..bottomBarLabel": "这些是匹配您的搜索的前 {maxItems} 个结果,请优化搜索以查看其他结果。", "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "正在显示第 {pageStart}-{pageEnd} 个 {type}(共 {total} 个)", @@ -12217,7 +12212,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "漏洞", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "漏洞", "xpack.csp.compactFormattedNumber.naTitle": "不可用", - "xpack.csp.complianceScoreChart.counterLink.failedFindingsTooltip": "失败的结果", "xpack.csp.complianceScoreChart.counterLink.passedFindingsTooltip": "通过的结果", "xpack.csp.createDetectionRuleButton": "创建检测规则", "xpack.csp.createPackagePolicy.customAssetsTab.cloudNativeVulnerabilityManagementTitleLabel": "云原生漏洞管理 ", @@ -12237,13 +12231,7 @@ "xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP", "xpack.csp.cspmIntegration.integration.nameTitle": "云安全态势管理", "xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM", - "xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "显示以下所有结果 ", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.accountNameTitle": "帐户名称", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.clusterNameTitle": "集群名称", "xpack.csp.dashboard.benchmarkSection.columnsHeader.complianceByCisSectionTitle": "合规性(按 CIS 部分)", - "xpack.csp.dashboard.benchmarkSection.columnsHeader.postureScoreTitle": "态势分数", - "xpack.csp.dashboard.benchmarkSection.defaultClusterTitle": "ID", - "xpack.csp.dashboard.benchmarkSection.manageRulesButton": "管理规则", "xpack.csp.dashboard.cspPageTemplate.pageTitle": "云安全态势", "xpack.csp.dashboard.risksTable.cisSectionColumnLabel": "CIS 部分", "xpack.csp.dashboard.risksTable.complianceColumnLabel": "合规性", From 8239363680096cc9993943ea0e5393614143e98e Mon Sep 17 00:00:00 2001 From: Omolola Akinleye Date: Thu, 30 Nov 2023 09:15:10 -0500 Subject: [PATCH 17/17] use constant interval and make code more readable --- .../server/routes/compliance_dashboard/get_benchmarks.ts | 9 ++++++--- .../server/routes/compliance_dashboard/get_trends.ts | 3 ++- .../server/tasks/findings_stats_task.ts | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts index 40bb076d7ca0a..b1f8335a61866 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_benchmarks.ts @@ -91,6 +91,8 @@ export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { return versions.map((version: BenchmarkVersionQueryResult) => { const benchmarkVersion = version.key; const assetCount = version.asset_count.value; + const resourcesTypesAggs = version.aggs_by_resource_type.buckets; + let benchmarkName = ''; if (!Array.isArray(version.aggs_by_benchmark_name.buckets)) @@ -99,15 +101,16 @@ export const getBenchmarksFromAggs = (benchmarks: BenchmarkBucket[]) => { if (version.aggs_by_benchmark_name && version.aggs_by_benchmark_name.buckets.length > 0) { benchmarkName = version.aggs_by_benchmark_name.buckets[0].key; } + + if (!Array.isArray(resourcesTypesAggs)) + throw new Error('missing aggs by resource type per benchmark'); + const { passed_findings: passedFindings, failed_findings: failedFindings } = version; const stats = getStatsFromFindingsEvaluationsAggs({ passed_findings: passedFindings, failed_findings: failedFindings, }); - const resourcesTypesAggs = version.aggs_by_resource_type.buckets; - if (!Array.isArray(resourcesTypesAggs)) - throw new Error('missing aggs by resource type per benchmark'); const groupedFindingsEvaluation = getPostureStatsFromAggs(resourcesTypesAggs); return { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts index 6ae25bf966552..00acd14d960fa 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_trends.ts @@ -10,6 +10,7 @@ import { calculatePostureScore } from '../../../common/utils/helpers'; import { BENCHMARK_SCORE_INDEX_DEFAULT_NS } from '../../../common/constants'; import type { PosturePolicyTemplate, Stats } from '../../../common/types'; import { toBenchmarkDocFieldKey } from '../../lib/mapping_field_util'; +import { CSPM_FINDINGS_STATS_INTERVAL } from '../../tasks/findings_stats_task'; interface FindingsDetails { total_findings: number; @@ -46,7 +47,7 @@ export type Trends = Array<{ export const getTrendsQuery = (policyTemplate: PosturePolicyTemplate) => ({ index: BENCHMARK_SCORE_INDEX_DEFAULT_NS, // Amount of samples of the last 24 hours (accounting that we take a sample every 5 minutes) - size: (24 * 60) / 5, + size: (24 * 60) / CSPM_FINDINGS_STATS_INTERVAL, sort: '@timestamp:desc', query: { bool: { diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts index 5820a4dcfedec..c157e8081546a 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts @@ -36,7 +36,7 @@ import { toBenchmarkMappingFieldKey } from '../lib/mapping_field_util'; const CSPM_FINDINGS_STATS_TASK_ID = 'cloud_security_posture-findings_stats'; const CSPM_FINDINGS_STATS_TASK_TYPE = 'cloud_security_posture-stats_task'; -const CSPM_FINDINGS_STATS_INTERVAL = '5m'; +export const CSPM_FINDINGS_STATS_INTERVAL = 5; export async function scheduleFindingsStatsTask( taskManager: TaskManagerStartContract, @@ -48,7 +48,7 @@ export async function scheduleFindingsStatsTask( id: CSPM_FINDINGS_STATS_TASK_ID, taskType: CSPM_FINDINGS_STATS_TASK_TYPE, schedule: { - interval: CSPM_FINDINGS_STATS_INTERVAL, + interval: `${CSPM_FINDINGS_STATS_INTERVAL}m`, }, state: emptyState, params: {},