mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Cloud Posture] CIS AWS - Cloud dashboard supports new findings fields (#150086)
This commit is contained in:
parent
3f4f1cb8c7
commit
eee58fa7f2
19 changed files with 205 additions and 119 deletions
|
@ -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>;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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 {};
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -26,6 +26,9 @@ export const benchmarkScoreMapping: MappingTypeMapping = {
|
|||
cluster_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
'cloud.account.id': {
|
||||
type: 'keyword',
|
||||
},
|
||||
'rule.benchmark.name': {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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(),
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 } }],
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue