[Cloud Posture] CIS AWS - Cloud dashboard supports new findings fields (#150086)

This commit is contained in:
Jordan 2023-02-02 20:24:25 +02:00 committed by GitHub
parent 3f4f1cb8c7
commit eee58fa7f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 205 additions and 119 deletions

View file

@ -12,11 +12,8 @@ import type { CspRuleTemplateMetadata } from './csp_rule_template_metadata';
export interface CspFinding {
'@timestamp': string;
cluster_id: string;
orchestrator?: {
cluster?: {
name?: string;
};
};
orchestrator?: CspFindingOrchestrator;
cloud?: CspFindingCloud; // only available on CSPM findings
result: CspFindingResult;
resource: CspFindingResource;
rule: CspRuleTemplateMetadata;
@ -28,6 +25,20 @@ export interface CspFinding {
};
}
interface CspFindingOrchestrator {
cluster?: {
name?: string;
};
}
interface CspFindingCloud {
provider: 'aws';
account: {
name: string;
id: string;
};
}
interface CspFindingResult {
evaluation: 'passed' | 'failed';
expected?: Record<string, unknown>;

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { schema as rt, TypeOf } from '@kbn/config-schema';
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../constants';
export const cspRuleTemplateMetadataSchemaV840 = rt.object({
audit: rt.string(),
@ -32,10 +33,12 @@ export const cspRuleTemplateMetadataSchemaV870 = rt.object({
audit: rt.string(),
benchmark: rt.object({
name: rt.string(),
posture_type: rt.maybe(
rt.oneOf([rt.literal(CSPM_POLICY_TEMPLATE), rt.literal(KSPM_POLICY_TEMPLATE)])
),
id: rt.string(),
version: rt.string(),
rule_number: rt.maybe(rt.string()),
posture_type: rt.maybe(rt.string()),
}),
default_value: rt.maybe(rt.string()),
description: rt.string(),

View file

@ -6,8 +6,9 @@
*/
import type { PackagePolicy, AgentPolicy } from '@kbn/fleet-plugin/common';
import { CspFinding } from './schemas/csp_finding';
import { SUPPORTED_CLOUDBEAT_INPUTS, SUPPORTED_POLICY_TEMPLATES } from './constants';
import type { CspRuleTemplateMetadata } from './schemas/csp_rule_template_metadata';
import { CspRuleTemplateMetadata } from './schemas/csp_rule_template_metadata';
export type Evaluation = 'passed' | 'failed' | 'NA';
/** number between 1-100 */
@ -34,10 +35,10 @@ export interface PostureTrend extends Stats {
export interface Cluster {
meta: {
clusterId: string;
clusterName?: string;
benchmarkName: string;
benchmarkId: BenchmarkId;
assetIdentifierId: string;
cloud: CspFinding['cloud'];
benchmark: CspFinding['rule']['benchmark'];
cluster: NonNullable<CspFinding['orchestrator']>['cluster'];
lastUpdate: string;
};
stats: Stats;

View file

@ -7,8 +7,12 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { useKibana } from '../hooks/use_kibana';
import { PosturePolicyTemplate, ComplianceDashboardData } from '../../../common/types';
import { STATS_ROUTE_PATH } from '../../../common/constants';
import { ComplianceDashboardData, PosturePolicyTemplate } from '../../../common/types';
import {
CSPM_POLICY_TEMPLATE,
KSPM_POLICY_TEMPLATE,
STATS_ROUTE_PATH,
} from '../../../common/constants';
// TODO: consolidate both hooks into one hook with a dynamic key
const getCspmStatsKey = ['csp_cspm_dashboard_stats'];
@ -24,8 +28,7 @@ export const useCspmStatsApi = (
const { http } = useKibana().services;
return useQuery(
getCspmStatsKey,
// TODO: CIS AWS - remove casting and use actual policy template instead of benchmark_id
() => http.get<ComplianceDashboardData>(getStatsRoute('cis_aws' as PosturePolicyTemplate)),
() => http.get<ComplianceDashboardData>(getStatsRoute(CSPM_POLICY_TEMPLATE)),
options
);
};
@ -36,8 +39,7 @@ export const useKspmStatsApi = (
const { http } = useKibana().services;
return useQuery(
getKspmStatsKey,
// TODO: CIS AWS - remove casting and use actual policy template
() => http.get<ComplianceDashboardData>(getStatsRoute('cis_k8s' as PosturePolicyTemplate)),
() => http.get<ComplianceDashboardData>(getStatsRoute(KSPM_POLICY_TEMPLATE)),
options
);
};

View file

@ -26,7 +26,7 @@ import {
DASHBOARD_CONTAINER,
KUBERNETES_DASHBOARD_CONTAINER,
} from './test_subjects';
import { useCspmStatsApi, useKspmStatsApi } from '../../common/api';
import { useCspmStatsApi, useKspmStatsApi } from '../../common/api/use_stats_api';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import { NoFindingsStates } from '../../components/no_findings_states';
import { SummarySection } from './dashboard_sections/summary_section';
@ -302,7 +302,6 @@ export const ComplianceDashboard = () => {
<div
data-test-subj={DASHBOARD_CONTAINER}
css={css`
max-width: 1600px;
margin-left: auto;
margin-right: auto;
height: 100%;

View file

@ -32,15 +32,15 @@ describe('<BenchmarksSection />', () => {
const mockDashboardDataCopy = getMockDashboardData();
const clusterMockDataCopy = getClusterMockData();
clusterMockDataCopy.stats.postureScore = 50;
clusterMockDataCopy.meta.clusterId = '1';
clusterMockDataCopy.meta.assetIdentifierId = '1';
const clusterMockDataCopy1 = getClusterMockData();
clusterMockDataCopy1.stats.postureScore = 95;
clusterMockDataCopy1.meta.clusterId = '2';
clusterMockDataCopy1.meta.assetIdentifierId = '2';
const clusterMockDataCopy2 = getClusterMockData();
clusterMockDataCopy2.stats.postureScore = 45;
clusterMockDataCopy2.meta.clusterId = '3';
clusterMockDataCopy2.meta.assetIdentifierId = '3';
mockDashboardDataCopy.clusters = [
clusterMockDataCopy,

View file

@ -7,20 +7,25 @@
import React, { useMemo } from 'react';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, useEuiTheme } from '@elastic/eui';
import type { EuiIconProps } 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 { CloudPostureScoreChart } from '../compliance_charts/cloud_posture_score_chart';
import type {
Cluster,
ComplianceDashboardData,
Evaluation,
PosturePolicyTemplate,
} from '../../../../common/types';
import { LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY } from '../../../common/constants';
import { RisksTable } from '../compliance_charts/risks_table';
import { KSPM_POLICY_TEMPLATE, RULE_FAILED } from '../../../../common/constants';
import {
CSPM_POLICY_TEMPLATE,
KSPM_POLICY_TEMPLATE,
RULE_FAILED,
} from '../../../../common/constants';
import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings';
import { ClusterDetailsBox } from './cluster_details_box';
import { dashboardColumnsGrow, getPolicyTemplateQuery } from './summary_section';
@ -31,6 +36,17 @@ import {
const CLUSTER_DEFAULT_SORT_ORDER = 'asc';
export const getClusterIdQuery = (cluster: Cluster) => {
if (cluster.meta.benchmark.posture_type === CSPM_POLICY_TEMPLATE) {
return { 'cloud.account.name': cluster.meta.cloud?.account.name };
}
if (cluster.meta.benchmark.posture_type === 'kspm') {
return { cluster_id: cluster.meta.assetIdentifierId };
}
return {};
};
export const BenchmarksSection = ({
complianceData,
dashboardType,
@ -50,25 +66,25 @@ export const BenchmarksSection = ({
const clusterSortingIcon: EuiIconProps['type'] = isClusterSortingAsc ? 'sortUp' : 'sortDown';
const navToFindingsByClusterAndEvaluation = (clusterId: string, evaluation: Evaluation) => {
const navToFindingsByClusterAndEvaluation = (cluster: Cluster, evaluation: Evaluation) => {
navToFindings({
...getPolicyTemplateQuery(dashboardType),
cluster_id: clusterId,
...getClusterIdQuery(cluster),
'result.evaluation': evaluation,
});
};
const navToFailedFindingsByClusterAndSection = (clusterId: string, ruleSection: string) => {
const navToFailedFindingsByClusterAndSection = (cluster: Cluster, ruleSection: string) => {
navToFindings({
...getPolicyTemplateQuery(dashboardType),
cluster_id: clusterId,
...getClusterIdQuery(cluster),
'rule.section': ruleSection,
'result.evaluation': RULE_FAILED,
});
};
const navToFailedFindingsByCluster = (clusterId: string) => {
navToFindingsByClusterAndEvaluation(clusterId, RULE_FAILED);
const navToFailedFindingsByCluster = (cluster: Cluster) => {
navToFindingsByClusterAndEvaluation(cluster, RULE_FAILED);
};
const toggleClustersSortingDirection = () => {
@ -144,8 +160,10 @@ export const BenchmarksSection = ({
</EuiFlexGroup>
{clusters.map((cluster) => (
<EuiFlexGroup
key={cluster.meta.clusterId}
key={cluster.meta.assetIdentifierId}
css={css`
// card height with 3 items in risk table
height: 200px;
border-bottom: ${euiTheme.border.thin};
padding: ${euiTheme.size.base} 0 ${euiTheme.size.l};
`}
@ -159,11 +177,11 @@ export const BenchmarksSection = ({
>
<CloudPostureScoreChart
compact
id={`${cluster.meta.clusterId}_score_chart`}
id={`${cluster.meta.assetIdentifierId}_score_chart`}
data={cluster.stats}
trend={cluster.trend}
onEvalCounterClick={(evaluation) =>
navToFindingsByClusterAndEvaluation(cluster.meta.clusterId, evaluation)
navToFindingsByClusterAndEvaluation(cluster, evaluation)
}
/>
</EuiFlexItem>
@ -173,13 +191,13 @@ export const BenchmarksSection = ({
data={cluster.groupedFindingsEvaluation}
maxItems={3}
onCellClick={(resourceTypeName) =>
navToFailedFindingsByClusterAndSection(cluster.meta.clusterId, resourceTypeName)
navToFailedFindingsByClusterAndSection(cluster, resourceTypeName)
}
viewAllButtonTitle={i18n.translate(
'xpack.csp.dashboard.risksTable.clusterCardViewAllButtonTitle',
{ defaultMessage: 'View all failed findings for this cluster' }
)}
onViewAllClick={() => navToFailedFindingsByCluster(cluster.meta.clusterId)}
onViewAllClick={() => navToFailedFindingsByCluster(cluster)}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -19,6 +19,7 @@ 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 { INTERNAL_FEATURE_FLAGS } from '../../../../common/constants';
import { Cluster } from '../../../../common/types';
import { useNavigateFindings } from '../../../common/hooks/use_navigate_findings';
@ -26,18 +27,23 @@ import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon';
const defaultClusterTitle = i18n.translate(
'xpack.csp.dashboard.benchmarkSection.defaultClusterTitle',
{ defaultMessage: 'Cluster ID' }
{ defaultMessage: 'ID' }
);
const getClusterTitle = (cluster: Cluster) => {
if (cluster.meta.benchmark.posture_type === 'cspm') return cluster.meta.cloud?.account.name;
if (cluster.meta.benchmark.posture_type === 'kspm') return cluster.meta.cluster?.name;
};
export const ClusterDetailsBox = ({ cluster }: { cluster: Cluster }) => {
const { euiTheme } = useEuiTheme();
const navToFindings = useNavigateFindings();
const shortId = cluster.meta.clusterId.slice(0, 6);
const title = cluster.meta.clusterName || defaultClusterTitle;
const shortId = cluster.meta.assetIdentifierId.slice(0, 6);
const title = getClusterTitle(cluster) || defaultClusterTitle;
const handleClusterTitleClick = (clusterId: string) => {
navToFindings({ cluster_id: clusterId });
const handleClusterTitleClick = () => {
return navToFindings(getClusterIdQuery(cluster));
};
return (
@ -64,7 +70,7 @@ export const ClusterDetailsBox = ({ cluster }: { cluster: Cluster }) => {
</EuiText>
}
>
<EuiLink onClick={() => handleClusterTitleClick(cluster.meta.clusterId)} color="text">
<EuiLink onClick={handleClusterTitleClick} color="text">
<EuiTitle css={{ fontSize: 20 }}>
<h5>
<FormattedMessage
@ -93,7 +99,7 @@ export const ClusterDetailsBox = ({ cluster }: { cluster: Cluster }) => {
grow={true}
style={{ justifyContent: 'flex-end', paddingBottom: euiTheme.size.m }}
>
<CISBenchmarkIcon type={cluster.meta.benchmarkId} name={cluster.meta.benchmarkName} />
<CISBenchmarkIcon type={cluster.meta.benchmark.id} name={cluster.meta.benchmark.name} />
</EuiFlexItem>
{INTERNAL_FEATURE_FLAGS.showManageRulesMock && (
<EuiFlexItem grow={false}>

View file

@ -36,10 +36,11 @@ export const dashboardColumnsGrow: Record<string, EuiFlexItemProps['grow']> = {
third: 8,
};
// TODO: CIS AWS - replace query to use policy_template field when available
export const getPolicyTemplateQuery = (policyTemplate: PosturePolicyTemplate) => {
if (policyTemplate === CSPM_POLICY_TEMPLATE) return { 'rule.benchmark.id': 'cis_aws' };
if (policyTemplate === KSPM_POLICY_TEMPLATE) return { 'rule.benchmark.id': 'cis_k8s' };
if (policyTemplate === CSPM_POLICY_TEMPLATE)
return { 'rule.benchmark.posture_type': CSPM_POLICY_TEMPLATE };
if (policyTemplate === KSPM_POLICY_TEMPLATE)
return { 'rule.benchmark.posture_type': KSPM_POLICY_TEMPLATE };
return {};
};

View file

@ -5,14 +5,29 @@
* 2.0.
*/
import { ComplianceDashboardData } from '../../../common/types';
import { Cluster, ComplianceDashboardData } from '../../../common/types';
export const getClusterMockData = () => ({
export const getClusterMockData = (): Cluster => ({
meta: {
clusterId: '8f9c5b98-cc02-4827-8c82-316e2cc25870',
benchmarkName: 'CIS Kubernetes V1.20',
assetIdentifierId: '8f9c5b98-cc02-4827-8c82-316e2cc25870',
lastUpdate: '2022-11-07T13:14:34.990Z',
benchmarkId: 'cis_k8s',
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',
},
},
stats: {
totalFailed: 17,

View file

@ -29,6 +29,8 @@ export const getFindingsFixture = (): CspFinding & { id: string } => ({
name: 'CIS Kubernetes',
version: '1.6.0',
id: 'cis_k8s',
rule_number: '1.1.1',
posture_type: 'kspm',
},
default_value: chance.sentence(),
description: chance.paragraph(),

View file

@ -26,6 +26,9 @@ export const benchmarkScoreMapping: MappingTypeMapping = {
cluster_id: {
type: 'keyword',
},
'cloud.account.id': {
type: 'keyword',
},
'rule.benchmark.name': {
type: 'keyword',
},

View file

@ -0,0 +1,52 @@
/*
* 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 { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
/**
* Creates the `asset_identifier` runtime field with the value of either
* `account.cloud.id` or `cluster.id` based on the value of `rule.benchmark.posture_type`
*/
export const getIdentifierRuntimeMapping = (): MappingRuntimeFields => ({
asset_identifier: {
type: 'keyword',
script: {
source: `
if (!doc.containsKey('rule.benchmark.posture_type'))
{
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
else
{
if(doc["rule.benchmark.posture_type"].size() > 0)
{
def policy_template_type = doc["rule.benchmark.posture_type"].value;
if (policy_template_type == "cspm")
{
def identifier = doc["cloud.account.id"].value;
emit(identifier);
return
}
if (policy_template_type == "kspm")
{
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
}
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
`,
},
},
});

View file

@ -6,7 +6,8 @@
*/
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import type { Logger } from '@kbn/core/server';
import type { MappingRuntimeFields, SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import { getIdentifierRuntimeMapping } from '../../get_identifier_runtime_mapping';
import { calculatePostureScore } from '../../../../common/utils/helpers';
import type { CspmAccountsStats } from './types';
import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants';
@ -52,47 +53,6 @@ interface AccountEntity {
};
}
// The runtime field help to have unique identifier for CSPM and KSPM
export const getIdentifierRuntimeMapping = (): MappingRuntimeFields => ({
asset_identifier: {
type: 'keyword',
script: {
source: `
if (!doc.containsKey('rule.benchmark.posture_type'))
{
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
else
{
if(doc["rule.benchmark.posture_type"].size() > 0)
{
def policy_template_type = doc["rule.benchmark.posture_type"].value;
if (policy_template_type == "cspm")
{
def identifier = doc["cloud.account.id"].value;
emit(identifier);
return
}
if (policy_template_type == "kspm")
{
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
}
def identifier = doc["cluster_id"].value;
emit(identifier);
return
}
`,
},
},
});
const getAccountsStatsQuery = (index: string): SearchRequest => ({
index,
runtime_mappings: getIdentifierRuntimeMapping(),

View file

@ -7,9 +7,9 @@
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import type { Logger } from '@kbn/core/server';
import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
import { getIdentifierRuntimeMapping } from '../../get_identifier_runtime_mapping';
import type { CspmResourcesStats } from './types';
import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants';
import { getIdentifierRuntimeMapping } from './accounts_stats_collector';
interface ResourcesStats {
accounts: {

View file

@ -9,7 +9,12 @@ import { transformError } from '@kbn/securitysolution-es-utils';
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { schema } from '@kbn/config-schema';
import type { PosturePolicyTemplate, ComplianceDashboardData } from '../../../common/types';
import { LATEST_FINDINGS_INDEX_DEFAULT_NS, STATS_ROUTE_PATH } from '../../../common/constants';
import {
CSPM_POLICY_TEMPLATE,
KSPM_POLICY_TEMPLATE,
LATEST_FINDINGS_INDEX_DEFAULT_NS,
STATS_ROUTE_PATH,
} from '../../../common/constants';
import { getGroupedFindingsEvaluation } from './get_grouped_findings_evaluation';
import { ClusterWithoutTrend, getClusters } from './get_clusters';
import { getStats } from './get_stats';
@ -26,7 +31,7 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends:
...cluster,
trend: trends.map(({ timestamp, clusters: clustersTrendData }) => ({
timestamp,
...clustersTrendData[cluster.meta.clusterId],
...clustersTrendData[cluster.meta.assetIdentifierId],
})),
}));
@ -35,8 +40,10 @@ const getSummaryTrend = (trends: Trends) =>
const queryParamsSchema = {
params: schema.object({
// TODO: CIS AWS - replace with strict policy template values once available
policy_template: schema.string(),
policy_template: schema.oneOf([
schema.literal(CSPM_POLICY_TEMPLATE),
schema.literal(KSPM_POLICY_TEMPLATE),
]),
}),
};
@ -64,8 +71,7 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter): void =>
const query: QueryDslQueryContainer = {
bool: {
// TODO: CIS AWS - replace filtered field to `policy_template` when available
filter: [{ term: { 'rule.benchmark.id': policyTemplate } }],
filter: [{ term: { 'rule.benchmark.posture_type': policyTemplate } }],
},
};

View file

@ -76,10 +76,13 @@ describe('getClustersFromAggs', () => {
{
meta: {
lastUpdate: '123',
clusterName: 'cluster_name',
clusterId: 'cluster_id',
benchmarkName: 'CIS Kubernetes',
benchmarkId: 'cis_k8s',
assetIdentifierId: 'cluster_id',
benchmark: { name: 'CIS Kubernetes', id: 'cis_k8s' },
cloud: undefined,
cluster: {
name: 'cluster_name',
},
},
stats: {
totalFindings: 12,

View file

@ -22,6 +22,7 @@ import {
import type { FailedFindingsQueryResult } from './get_grouped_findings_evaluation';
import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats';
import { KeyDocCount } from './compliance_dashboard';
import { getIdentifierRuntimeMapping } from '../../lib/get_identifier_runtime_mapping';
export interface ClusterBucket extends FailedFindingsQueryResult, KeyDocCount {
failed_findings: {
@ -34,18 +35,19 @@ export interface ClusterBucket extends FailedFindingsQueryResult, KeyDocCount {
}
interface ClustersQueryResult {
aggs_by_cluster_id: Aggregation<ClusterBucket>;
aggs_by_asset_identifier: Aggregation<ClusterBucket>;
}
export type ClusterWithoutTrend = Omit<Cluster, 'trend'>;
export const getClustersQuery = (query: QueryDslQueryContainer, pitId: string): SearchRequest => ({
size: 0,
runtime_mappings: getIdentifierRuntimeMapping(),
query,
aggs: {
aggs_by_cluster_id: {
aggs_by_asset_identifier: {
terms: {
field: 'cluster_id',
field: 'asset_identifier',
},
aggs: {
latestFindingTopHit: {
@ -65,25 +67,26 @@ export const getClustersQuery = (query: QueryDslQueryContainer, pitId: string):
});
export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTrend[] =>
clusters.map((cluster) => {
const latestFindingHit: SearchHit<CspFinding> = cluster.latestFindingTopHit.hits.hits[0];
clusters.map((clusterBucket) => {
const latestFindingHit: SearchHit<CspFinding> = clusterBucket.latestFindingTopHit.hits.hits[0];
if (!latestFindingHit._source) throw new Error('Missing findings top hits');
const meta = {
clusterId: cluster.key,
clusterName: latestFindingHit._source.orchestrator?.cluster?.name,
benchmarkName: latestFindingHit._source.rule.benchmark.name,
benchmarkId: latestFindingHit._source.rule.benchmark.id,
clusterId: clusterBucket.key,
assetIdentifierId: clusterBucket.key,
lastUpdate: latestFindingHit._source['@timestamp'],
benchmark: latestFindingHit._source.rule.benchmark,
cloud: latestFindingHit._source.cloud, // only available on CSPM findings
cluster: latestFindingHit._source.orchestrator?.cluster, // only available on KSPM findings
};
// get cluster's stats
if (!cluster.failed_findings || !cluster.passed_findings)
throw new Error('missing findings evaluations per cluster');
const stats = getStatsFromFindingsEvaluationsAggs(cluster);
if (!clusterBucket.failed_findings || !clusterBucket.passed_findings)
throw new Error('missing findings evaluations per cluster bucket');
const stats = getStatsFromFindingsEvaluationsAggs(clusterBucket);
// get cluster's resource types aggs
const resourcesTypesAggs = cluster.aggs_by_resource_type.buckets;
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);
@ -104,7 +107,7 @@ export const getClusters = async (
getClustersQuery(query, pitId)
);
const clusters = queryResult.aggregations?.aggs_by_cluster_id.buckets;
const clusters = queryResult.aggregations?.aggs_by_asset_identifier.buckets;
if (!Array.isArray(clusters)) throw new Error('missing aggs by cluster id');
return getClustersFromAggs(clusters);

View file

@ -14,6 +14,7 @@ import {
import { SearchRequest } from '@kbn/data-plugin/common';
import { ElasticsearchClient } from '@kbn/core/server';
import type { Logger } from '@kbn/core/server';
import { getIdentifierRuntimeMapping } from '../lib/get_identifier_runtime_mapping';
import { FindingsStatsTaskResult, TaskHealthStatus, ScoreByPolicyTemplateBucket } from './types';
import {
BENCHMARK_SCORE_INDEX_DEFAULT_NS,
@ -107,14 +108,14 @@ export function taskRunner(coreStartServices: CspServerPluginStartServices, logg
const getScoreQuery = (): SearchRequest => ({
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
size: 0,
runtime_mappings: getIdentifierRuntimeMapping(),
query: {
match_all: {},
},
aggs: {
score_by_policy_template: {
terms: {
// TODO: CIS AWS - replace with policy_template when available
field: 'rule.benchmark.id',
field: 'rule.benchmark.posture_type',
},
aggs: {
total_findings: {
@ -138,7 +139,7 @@ const getScoreQuery = (): SearchRequest => ({
},
score_by_cluster_id: {
terms: {
field: 'cluster_id',
field: 'asset_identifier',
},
aggregations: {
total_findings: {