mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Security] Telemetry by rule
This commit is contained in:
parent
8693c622db
commit
33b6dc9b1b
6 changed files with 342 additions and 13 deletions
|
@ -6,7 +6,10 @@
|
|||
*/
|
||||
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 type {
|
||||
AggregationsMultiBucketBase,
|
||||
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';
|
||||
|
@ -15,11 +18,6 @@ import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants';
|
|||
interface Value {
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface DocCount {
|
||||
doc_count: number;
|
||||
}
|
||||
|
||||
interface BenchmarkName {
|
||||
metrics: { 'rule.benchmark.name': string };
|
||||
}
|
||||
|
@ -40,8 +38,8 @@ interface AccountsStats {
|
|||
interface AccountEntity {
|
||||
key: string; // account_id
|
||||
doc_count: number; // latest findings doc count
|
||||
passed_findings_count: DocCount;
|
||||
failed_findings_count: DocCount;
|
||||
passed_findings_count: AggregationsMultiBucketBase;
|
||||
failed_findings_count: AggregationsMultiBucketBase;
|
||||
benchmark_name: { top: BenchmarkName[] };
|
||||
benchmark_id: { top: BenchmarkId[] };
|
||||
benchmark_version: { top: BenchmarkVersion[] };
|
||||
|
@ -53,8 +51,8 @@ interface AccountEntity {
|
|||
};
|
||||
}
|
||||
|
||||
const getAccountsStatsQuery = (index: string): SearchRequest => ({
|
||||
index,
|
||||
const getAccountsStatsQuery = (): SearchRequest => ({
|
||||
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
runtime_mappings: getIdentifierRuntimeMapping(),
|
||||
query: {
|
||||
match_all: {},
|
||||
|
@ -225,7 +223,7 @@ export const getAccountsStats = async (
|
|||
|
||||
if (isIndexExists) {
|
||||
const accountsStatsResponse = await esClient.search<unknown, AccountsStats>(
|
||||
getAccountsStatsQuery(LATEST_FINDINGS_INDEX_DEFAULT_NS)
|
||||
getAccountsStatsQuery()
|
||||
);
|
||||
|
||||
const cspmAccountsStats = accountsStatsResponse.aggregations
|
||||
|
@ -237,7 +235,7 @@ export const getAccountsStats = async (
|
|||
|
||||
return [];
|
||||
} catch (e) {
|
||||
logger.error(`Failed to get resources stats ${e}`);
|
||||
logger.error(`Failed to get account stats ${e}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getResourcesStats } from './resources_stats_collector';
|
|||
import { cspmUsageSchema } from './schema';
|
||||
import { CspmUsage } from './types';
|
||||
import { getAccountsStats } from './accounts_stats_collector';
|
||||
import { getRulesStats } from './rules_stats_collector';
|
||||
|
||||
export function registerCspmUsageCollector(
|
||||
logger: Logger,
|
||||
|
@ -27,16 +28,18 @@ export function registerCspmUsageCollector(
|
|||
type: 'cloud_security_posture',
|
||||
isReady: () => true,
|
||||
fetch: async (collectorFetchContext: CollectorFetchContext) => {
|
||||
const [indicesStats, accountsStats, resourcesStats] = await Promise.all([
|
||||
const [indicesStats, accountsStats, resourcesStats, rulesStats] = await Promise.all([
|
||||
getIndicesStats(collectorFetchContext.esClient, logger),
|
||||
getAccountsStats(collectorFetchContext.esClient, logger),
|
||||
getResourcesStats(collectorFetchContext.esClient, logger),
|
||||
getRulesStats(collectorFetchContext.esClient, logger),
|
||||
]);
|
||||
|
||||
return {
|
||||
indices: indicesStats,
|
||||
accounts_stats: accountsStats,
|
||||
resources_stats: resourcesStats,
|
||||
rules_stats: rulesStats,
|
||||
};
|
||||
},
|
||||
schema: cspmUsageSchema,
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* 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 type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type {
|
||||
AggregationsMultiBucketBase,
|
||||
SearchRequest,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { CspmRulesStats } from './types';
|
||||
import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants';
|
||||
|
||||
interface BenchmarkName {
|
||||
metrics: { 'rule.benchmark.name': string };
|
||||
}
|
||||
|
||||
interface BenchmarkId {
|
||||
metrics: { 'rule.benchmark.id': string };
|
||||
}
|
||||
|
||||
interface BenchmarkVersion {
|
||||
metrics: { 'rule.benchmark.version': string };
|
||||
}
|
||||
interface RuleName {
|
||||
metrics: { 'rule.name': string };
|
||||
}
|
||||
interface RuleSection {
|
||||
metrics: { 'rule.section': string };
|
||||
}
|
||||
interface RuleVersion {
|
||||
metrics: { 'rule.version': string };
|
||||
}
|
||||
interface RuleNumber {
|
||||
metrics: { 'rule.benchmark.rule_number': string };
|
||||
}
|
||||
interface PostureType {
|
||||
metrics: { 'rule.benchmark.posture_type': string };
|
||||
}
|
||||
|
||||
interface RulesStats {
|
||||
rules: {
|
||||
buckets: RuleEntity[];
|
||||
};
|
||||
}
|
||||
|
||||
interface RuleEntity {
|
||||
key: string; // rule_id
|
||||
rule_name: { top: RuleName[] };
|
||||
rule_section: { top: RuleSection[] };
|
||||
rule_version: { top: RuleVersion[] };
|
||||
rule_number: { top: RuleNumber[] };
|
||||
posture_type: { top: PostureType[] };
|
||||
benchmark_id: { top: BenchmarkId[] };
|
||||
benchmark_name: { top: BenchmarkName[] };
|
||||
benchmark_version: { top: BenchmarkVersion[] };
|
||||
passed_findings_count: AggregationsMultiBucketBase;
|
||||
failed_findings_count: AggregationsMultiBucketBase;
|
||||
}
|
||||
|
||||
const getRulesStatsQuery = (): SearchRequest => ({
|
||||
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
aggs: {
|
||||
rules: {
|
||||
terms: {
|
||||
field: 'rule.id',
|
||||
order: {
|
||||
_count: 'desc',
|
||||
},
|
||||
size: 100,
|
||||
},
|
||||
aggs: {
|
||||
rule_name: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.name',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
rule_section: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.section',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
rule_version: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.version',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
posture_type: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.benchmark.posture_type',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
rule_number: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.benchmark.rule_number',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
benchmark_id: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.benchmark.id',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
benchmark_version: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.benchmark.version',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
benchmark_name: {
|
||||
top_metrics: {
|
||||
metrics: {
|
||||
field: 'rule.benchmark.name',
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
passed_findings_count: {
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'result.evaluation': 'passed',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
failed_findings_count: {
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'result.evaluation': 'failed',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
size: 0,
|
||||
_source: false,
|
||||
});
|
||||
|
||||
const getCspmRulesStats = (aggregatedRulesStats: RulesStats, logger: Logger): CspmRulesStats[] => {
|
||||
const rules = aggregatedRulesStats.rules.buckets;
|
||||
|
||||
const cspmRulesStats = rules.map((rule) => ({
|
||||
rule_id: rule.key,
|
||||
rule_name: rule.rule_name.top[0].metrics['rule.name'],
|
||||
rule_section: rule.rule_section.top[0].metrics['rule.section'],
|
||||
rule_version: rule.rule_version.top[0].metrics['rule.version'],
|
||||
rule_number: rule.rule_number.top[0].metrics['rule.benchmark.rule_number'],
|
||||
posture_type: rule.posture_type.top[0].metrics['rule.benchmark.posture_type'],
|
||||
benchmark_name: rule.benchmark_name.top[0].metrics['rule.benchmark.name'],
|
||||
benchmark_id: rule.benchmark_id.top[0].metrics['rule.benchmark.id'],
|
||||
passed_findings_count: rule.passed_findings_count.doc_count,
|
||||
benchmark_version: rule.benchmark_version.top[0].metrics['rule.benchmark.version'],
|
||||
failed_findings_count: rule.failed_findings_count.doc_count,
|
||||
}));
|
||||
|
||||
return cspmRulesStats;
|
||||
};
|
||||
|
||||
export const getRulesStats = async (
|
||||
esClient: ElasticsearchClient,
|
||||
logger: Logger
|
||||
): Promise<CspmRulesStats[]> => {
|
||||
try {
|
||||
const isIndexExists = await esClient.indices.exists({
|
||||
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
});
|
||||
|
||||
if (isIndexExists) {
|
||||
const rulesStatsResponse = await esClient.search<unknown, RulesStats>(getRulesStatsQuery());
|
||||
|
||||
const cspmRulesStats = rulesStatsResponse.aggregations
|
||||
? getCspmRulesStats(rulesStatsResponse.aggregations, logger)
|
||||
: [];
|
||||
|
||||
return cspmRulesStats;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
logger.error(`Failed to get rules stats ${e}`);
|
||||
return [];
|
||||
}
|
||||
};
|
|
@ -81,4 +81,20 @@ export const cspmUsageSchema: MakeSchemaFrom<CspmUsage> = {
|
|||
pods_count: { type: 'short' },
|
||||
},
|
||||
},
|
||||
rules_stats: {
|
||||
type: 'array',
|
||||
items: {
|
||||
rule_id: { type: 'keyword' },
|
||||
rule_name: { type: 'keyword' },
|
||||
rule_section: { type: 'keyword' },
|
||||
rule_version: { type: 'keyword' },
|
||||
rule_number: { type: 'keyword' },
|
||||
posture_type: { type: 'keyword' },
|
||||
benchmark_id: { type: 'keyword' },
|
||||
benchmark_name: { type: 'keyword' },
|
||||
benchmark_version: { type: 'keyword' },
|
||||
passed_findings_count: { type: 'long' },
|
||||
failed_findings_count: { type: 'long' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface CspmUsage {
|
|||
indices: CspmIndicesStats;
|
||||
resources_stats: CspmResourcesStats[];
|
||||
accounts_stats: CspmAccountsStats[];
|
||||
rules_stats: CspmRulesStats[];
|
||||
}
|
||||
|
||||
export interface CspmIndicesStats {
|
||||
|
@ -46,3 +47,16 @@ export interface CspmAccountsStats {
|
|||
nodes_count: number;
|
||||
pods_count: number;
|
||||
}
|
||||
export interface CspmRulesStats {
|
||||
rule_id: string;
|
||||
rule_name: string;
|
||||
rule_section: string;
|
||||
rule_version: string;
|
||||
rule_number: string;
|
||||
posture_type: string;
|
||||
benchmark_id: string;
|
||||
benchmark_name: string;
|
||||
benchmark_version: string;
|
||||
passed_findings_count: number;
|
||||
failed_findings_count: number;
|
||||
}
|
||||
|
|
|
@ -5183,6 +5183,46 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules_stats": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"rule_id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"rule_name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"rule_section": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"rule_version": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"rule_number": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"posture_type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"benchmark_id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"benchmark_name": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"benchmark_version": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"passed_findings_count": {
|
||||
"type": "long"
|
||||
},
|
||||
"failed_findings_count": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue