[9.0] [Security Solution][Detection Engine] adds spaces telemetry for detection rules (#215393) (#216586)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Security Solution][Detection Engine] adds spaces telemetry for
detection rules
(#215393)](https://github.com/elastic/kibana/pull/215393)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Vitalii
Dmyterko","email":"92328789+vitaliidm@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-04-01T08:07:08Z","message":"[Security
Solution][Detection Engine] adds spaces telemetry for detection rules
(#215393)\n\n## Summary\n\n - addresses
https://github.com/elastic/security-team/issues/12000\n - adds telemetry
for rules in spaces: \n - number of spaces, detection rules added\n -
number of rules in each space\n\n---------\n\nCo-authored-by: Dan
Dillinger <ddillinger@users.noreply.github.com>\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b3d750bc49816de62de75bd550e7aab0066c7b7f","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection
Engine","backport:version","v9.1.0","v8.19.0"],"title":"[Security
Solution][Detection Engine] adds spaces telemetry for detection
rules","number":215393,"url":"https://github.com/elastic/kibana/pull/215393","mergeCommit":{"message":"[Security
Solution][Detection Engine] adds spaces telemetry for detection rules
(#215393)\n\n## Summary\n\n - addresses
https://github.com/elastic/security-team/issues/12000\n - adds telemetry
for rules in spaces: \n - number of spaces, detection rules added\n -
number of rules in each space\n\n---------\n\nCo-authored-by: Dan
Dillinger <ddillinger@users.noreply.github.com>\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b3d750bc49816de62de75bd550e7aab0066c7b7f"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/215393","number":215393,"mergeCommit":{"message":"[Security
Solution][Detection Engine] adds spaces telemetry for detection rules
(#215393)\n\n## Summary\n\n - addresses
https://github.com/elastic/security-team/issues/12000\n - adds telemetry
for rules in spaces: \n - number of spaces, detection rules added\n -
number of rules in each space\n\n---------\n\nCo-authored-by: Dan
Dillinger <ddillinger@users.noreply.github.com>\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"b3d750bc49816de62de75bd550e7aab0066c7b7f"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2025-04-01 12:09:51 +02:00 committed by GitHub
parent 471ef15a70
commit 3c0fe5e9d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 118 additions and 3 deletions

View file

@ -431,6 +431,25 @@
"properties": {
"detection_rules": {
"properties": {
"spaces_usage": {
"properties": {
"total": {
"type": "long",
"_meta": {
"description": "Total number of spaces where detection rules added"
}
},
"rules_in_spaces": {
"type": "array",
"items": {
"type": "long",
"_meta": {
"description": "Number of rules is each space"
}
}
}
}
},
"detection_rule_usage": {
"properties": {
"query": {

View file

@ -44,6 +44,19 @@ export const registerCollector: RegisterCollector = ({
schema: {
detectionMetrics: {
detection_rules: {
spaces_usage: {
total: {
type: 'long',
_meta: { description: 'Total number of spaces where detection rules added' },
},
rules_in_spaces: {
type: 'array',
items: {
type: 'long',
_meta: { description: 'Number of rules is each space' },
},
},
},
detection_rule_usage: {
query: {
enabled: { type: 'long', _meta: { description: 'Number of query rules enabled' } },

View file

@ -8,7 +8,11 @@
import type { DetectionMetrics } from './types';
import { getInitialMlJobUsage } from './ml_jobs/get_initial_usage';
import { getInitialEventLogUsage, getInitialRulesUsage } from './rules/get_initial_usage';
import {
getInitialEventLogUsage,
getInitialRulesUsage,
getInitialSpacesUsage,
} from './rules/get_initial_usage';
// eslint-disable-next-line no-restricted-imports
import { getInitialLegacySiemSignalsUsage } from './legacy_siem_signals/get_initial_usage';
@ -24,6 +28,7 @@ export const getInitialDetectionMetrics = (): DetectionMetrics => ({
detection_rule_detail: [],
detection_rule_usage: getInitialRulesUsage(),
detection_rule_status: getInitialEventLogUsage(),
spaces_usage: getInitialSpacesUsage(),
},
legacy_siem_signals: getInitialLegacySiemSignalsUsage(),
});

View file

@ -86,6 +86,10 @@ describe('Detections Usage and Metrics', () => {
expect(result).toEqual<DetectionMetrics>({
...getInitialDetectionMetrics(),
detection_rules: {
spaces_usage: {
rules_in_spaces: [1],
total: 1,
},
detection_rule_status: getAllEventLogTransform(),
detection_rule_detail: [
{
@ -162,6 +166,10 @@ describe('Detections Usage and Metrics', () => {
expect(result).toEqual<DetectionMetrics>({
...getInitialDetectionMetrics(),
detection_rules: {
spaces_usage: {
rules_in_spaces: [1],
total: 1,
},
detection_rule_status: getAllEventLogTransform(),
detection_rule_detail: [], // *should not* contain custom detection rule details
detection_rule_usage: {
@ -219,6 +227,10 @@ describe('Detections Usage and Metrics', () => {
expect(result).toEqual<DetectionMetrics>({
...getInitialDetectionMetrics(),
detection_rules: {
spaces_usage: {
rules_in_spaces: [1],
total: 1,
},
detection_rule_status: getAllEventLogTransform(),
detection_rule_detail: [
{

View file

@ -11,7 +11,11 @@ import type { DetectionMetrics } from './types';
import { getMlJobMetrics } from './ml_jobs/get_metrics';
import { getRuleMetrics } from './rules/get_metrics';
import { getInitialEventLogUsage, getInitialRulesUsage } from './rules/get_initial_usage';
import {
getInitialEventLogUsage,
getInitialRulesUsage,
getInitialSpacesUsage,
} from './rules/get_initial_usage';
import { getInitialMlJobUsage } from './ml_jobs/get_initial_usage';
// eslint-disable-next-line no-restricted-imports
import { getInitialLegacySiemSignalsUsage } from './legacy_siem_signals/get_initial_usage';
@ -55,6 +59,7 @@ export const getDetectionsMetrics = async ({
detection_rule_detail: [],
detection_rule_usage: getInitialRulesUsage(),
detection_rule_status: getInitialEventLogUsage(),
spaces_usage: getInitialSpacesUsage(),
},
legacy_siem_signals:
legacySiemSignalsUsage.status === 'fulfilled'

View file

@ -12,6 +12,7 @@ import type {
SingleEventLogStatusMetric,
SingleEventMetric,
AlertSuppressionUsage,
SpacesUsage,
} from './types';
export const initialAlertSuppression: AlertSuppressionUsage = {
@ -28,6 +29,11 @@ export const initialAlertSuppression: AlertSuppressionUsage = {
does_not_suppress_missing_fields: 0,
};
export const getInitialSpacesUsage = (): SpacesUsage => ({
total: 0,
rules_in_spaces: [],
});
/**
* Default detection rule usage count, split by type + elastic/custom
*/

View file

@ -12,11 +12,16 @@ import { updateRuleUsage } from './update_usage';
import { getDetectionRules } from '../../queries/get_detection_rules';
import { getAlerts } from '../../queries/get_alerts';
import { MAX_PER_PAGE, MAX_RESULTS_WINDOW } from '../../constants';
import { getInitialEventLogUsage, getInitialRulesUsage } from './get_initial_usage';
import {
getInitialEventLogUsage,
getInitialRulesUsage,
getInitialSpacesUsage,
} from './get_initial_usage';
import { getCaseComments } from '../../queries/get_case_comments';
import { getRuleIdToCasesMap } from './transform_utils/get_rule_id_to_cases_map';
import { getAlertIdToCountMap } from './transform_utils/get_alert_id_to_count_map';
import { getRuleIdToEnabledMap } from './transform_utils/get_rule_id_to_enabled_map';
import { getSpacesUsage } from './transform_utils/get_spaces_usage';
import { getRuleObjectCorrelations } from './transform_utils/get_rule_object_correlations';
import { getEventLogByTypeAndStatus } from '../../queries/get_event_log_by_type_and_status';
@ -53,6 +58,7 @@ export const getRuleMetrics = async ({
detection_rule_detail: [],
detection_rule_usage: getInitialRulesUsage(),
detection_rule_status: getInitialEventLogUsage(),
spaces_usage: getInitialSpacesUsage(),
};
}
@ -123,6 +129,7 @@ export const getRuleMetrics = async ({
detection_rule_detail: elasticRuleObjects,
detection_rule_usage: rulesUsage,
detection_rule_status: eventLogMetricsTypeStatus,
spaces_usage: getSpacesUsage(ruleResults),
};
} catch (e) {
// ignore failure, usage will be zeroed. We use debug mode to not unnecessarily worry users as this will not effect them.
@ -133,6 +140,7 @@ export const getRuleMetrics = async ({
detection_rule_detail: [],
detection_rule_usage: getInitialRulesUsage(),
detection_rule_status: getInitialEventLogUsage(),
spaces_usage: getInitialSpacesUsage(),
};
}
};

View file

@ -0,0 +1,41 @@
/*
* 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 { SavedObjectsFindResult } from '@kbn/core/server';
import type { RuleSearchResult } from '../../../types';
import type { SpacesUsage } from '../types';
/**
* Calculates the usage of spaces based on the provided rule results.
*
* @param {Array<SavedObjectsFindResult<RuleSearchResult>>} ruleResults - An array of saved object results containing rule search results.
*
* @returns {SpacesUsage}
* - `total`: The total number spaces rules belong to.
* - `rules_in_spaces`: An array where each value represents the number of rules in a specific space.
*/
export const getSpacesUsage = (
ruleResults: Array<SavedObjectsFindResult<RuleSearchResult>>
): SpacesUsage => {
const spacesUsageMap = new Map<string, number>();
// for loop is faster
for (let i = 0; i < ruleResults.length; i++) {
const rule = ruleResults[i];
const space = rule.namespaces?.[0];
if (space) {
spacesUsageMap.set(space, (spacesUsageMap.get(space) ?? 0) + 1);
}
}
return {
total: spacesUsageMap.size,
rules_in_spaces: Array.from(spacesUsageMap.values()),
};
};

View file

@ -44,10 +44,16 @@ export interface RulesTypeUsage {
esql: FeatureTypeUsage;
}
export interface SpacesUsage {
total: number;
rules_in_spaces: number[];
}
export interface RuleAdoption {
detection_rule_detail: RuleMetric[];
detection_rule_usage: RulesTypeUsage;
detection_rule_status: EventLogStatusMetric;
spaces_usage: SpacesUsage;
}
export interface RuleMetric {