[Cloud Security] Telemetry by rule

This commit is contained in:
Ido Cohen 2023-02-07 18:23:22 +02:00 committed by GitHub
parent 8693c622db
commit 33b6dc9b1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 342 additions and 13 deletions

View file

@ -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 [];
}
};

View file

@ -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,

View file

@ -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 [];
}
};

View file

@ -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' },
},
},
};

View file

@ -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;
}

View file

@ -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"
}
}
}
}
}
},