diff --git a/x-pack/platform/test/plugin_api_integration/config.ts b/x-pack/platform/test/plugin_api_integration/config.ts index f0be0d2bd8eec..492dbbdee1323 100644 --- a/x-pack/platform/test/plugin_api_integration/config.ts +++ b/x-pack/platform/test/plugin_api_integration/config.ts @@ -38,6 +38,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--xpack.task_manager.monitored_aggregated_stats_refresh_rate=5000', '--xpack.task_manager.ephemeral_tasks.enabled=false', '--xpack.task_manager.ephemeral_tasks.request_capacity=100', + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'responseActionsTelemetryEnabled', + ])}`, `--xpack.stack_connectors.enableExperimental=${JSON.stringify([ 'crowdstrikeConnectorOn', 'microsoftDefenderEndpointOn', diff --git a/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index de230f81daed3..8f94a18bda2d0 100644 --- a/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/platform/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -186,6 +186,7 @@ export default function ({ getService }: FtrProviderContext) { 'security:telemetry-filterlist-artifact', 'security:telemetry-lists', 'security:telemetry-prebuilt-rule-alerts', + 'security:telemetry-response-actions-rules', 'security:telemetry-timelines', 'session_cleanup', 'slo:bulk-delete-task', diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index e738815ac2553..bfc3b6b2bff6d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -131,6 +131,9 @@ export const createMockTelemetryReceiver = ( fetchTrustedApplications: jest.fn(), fetchEndpointList: jest.fn(), fetchDetectionRules: jest.fn().mockReturnValue({ body: null }), + fetchResponseActionsRules: jest + .fn() + .mockReturnValue({ body: { aggregations: { actionTypes: {} } } }), fetchEndpointMetadata: jest.fn().mockReturnValue(Promise.resolve(new Map())), fetchTimelineAlerts: jest.fn().mockReturnValue(Promise.resolve(stubEndpointAlertResponse())), buildProcessTree: jest.fn().mockReturnValue(processTreeResponse), diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/async_sender.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/async_sender.test.ts index 5545983ec9c17..64d9c2c8962f6 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/async_sender.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/async_sender.test.ts @@ -21,6 +21,7 @@ import { createMockUsageCounter, } from './__mocks__'; import { TelemetryEventsSender } from './sender'; +import type { ExperimentalFeatures } from '../../../common'; jest.mock('axios'); jest.mock('./receiver'); @@ -1006,7 +1007,10 @@ describe('AsyncTelemetryEventsSender', () => { describe('ITelemetryEventsSender integration', () => { it('should send events using the async service', async () => { - const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + const serviceV1 = new TelemetryEventsSender( + loggingSystemMock.createLogger(), + {} as ExperimentalFeatures + ); service.setup(DEFAULT_RETRY_CONFIG, DEFAULT_QUEUE_CONFIG, receiver, telemetryPluginSetup); service.start(telemetryPluginStart); @@ -1038,7 +1042,10 @@ describe('AsyncTelemetryEventsSender', () => { const bufferTimeSpanMillis = initialTimeSpan * 10; const events = ['e1', 'e2', 'e3']; const expectedBody = events.map((e) => JSON.stringify(e)).join('\n'); - const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + const serviceV1 = new TelemetryEventsSender( + loggingSystemMock.createLogger(), + {} as ExperimentalFeatures + ); serviceV1.setup(receiver, telemetryPluginSetup, undefined, telemetryUsageCounter, service); @@ -1077,7 +1084,10 @@ describe('AsyncTelemetryEventsSender', () => { ...detectionAlertsBefore, bufferTimeSpanMillis: 5001, }; - const serviceV1 = new TelemetryEventsSender(loggingSystemMock.createLogger()); + const serviceV1 = new TelemetryEventsSender( + loggingSystemMock.createLogger(), + {} as ExperimentalFeatures + ); serviceV1.setup(receiver, telemetryPluginSetup, undefined, telemetryUsageCounter, service); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/helpers.ts index 5250b57f9d658..bde93c4a84595 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -24,6 +24,8 @@ import type { ExtraInfo, ListTemplate, Nullable, + ResponseActionsRuleTelemetryTemplate, + ResponseActionRules, TelemetryEvent, TimeFrame, TimelineResult, @@ -236,6 +238,34 @@ export const templateExceptionList = ( }); }; +/** + * Constructs the response actions custom rule telemetry schema from a list of rule params + * */ +export const responseActionsCustomRuleTelemetryData = ( + responseActionsRules: ResponseActionRules, + clusterInfo: ESClusterInfo, + licenseInfo: Nullable +): ResponseActionsRuleTelemetryTemplate => { + const baseTelemetryData: ResponseActionsRuleTelemetryTemplate = { + '@timestamp': moment().toISOString(), + cluster_uuid: clusterInfo.cluster_uuid, + cluster_name: clusterInfo.cluster_name, + license_id: licenseInfo?.uid, + response_actions_rules: { + endpoint: 0, + osquery: 0, + }, + }; + + return { + ...baseTelemetryData, + response_actions_rules: { + endpoint: responseActionsRules.endpoint, + osquery: responseActionsRules.osquery, + }, + }; +}; + /** * Convert counter label list to kebab case * diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/receiver.ts index 7d0fb1501697f..f866182da2ced 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -54,6 +54,8 @@ import type { } from '@kbn/fleet-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import moment from 'moment'; + +import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server'; import type { ExperimentalFeatures } from '../../../common'; import type { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services'; import { @@ -219,6 +221,13 @@ export interface ITelemetryReceiver { > >; + fetchResponseActionsRules( + executeFrom: string, + executeTo: string + ): Promise< + TransportResult>, unknown> + >; + fetchDetectionExceptionList( listId: string, ruleVersion: number @@ -749,6 +758,78 @@ export class TelemetryReceiver implements ITelemetryReceiver { return this.esClient().search(query, { meta: true }); } + /** + * Find elastic rules SOs which are the rules that have immutable set to true and are of a particular rule type + * @returns custom elastic rules SOs with response actions enabled + */ + public async fetchResponseActionsRules(executeFrom: string, executeTo: string) { + const query: SearchRequest = { + index: `${this.getIndexForType?.(RULE_SAVED_OBJECT_TYPE)}`, + ignore_unavailable: true, + size: 0, // no query results required - only aggregation quantity + from: 0, + query: { + bool: { + must: [ + { + term: { + type: 'alert', + }, + }, + { + term: { + 'alert.params.immutable': { + value: false, + }, + }, + }, + { + term: { + 'alert.enabled': { + value: true, + }, + }, + }, + { + terms: { + 'alert.consumer': ['siem', 'securitySolution'], + }, + }, + { + terms: { + 'alert.params.responseActions.actionTypeId': ['.endpoint', '.osquery'], + }, + }, + { + range: { + 'alert.updatedAt': { + gte: executeFrom, + lte: executeTo, + }, + }, + }, + ], + }, + }, + sort: [ + { + 'alert.updatedAt': { + order: 'desc', + }, + }, + ], + aggs: { + actionTypes: { + terms: { + field: 'alert.params.responseActions.actionTypeId', + }, + }, + }, + }; + + return this.esClient().search(query, { meta: true }); + } + public async fetchDetectionExceptionList(listId: string, ruleVersion: number) { if (this?.exceptionListClient === undefined || this?.exceptionListClient === null) { throw Error('exception list client is unavailable: could not retrieve trusted applications'); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.test.ts index 648bb8358e1d2..c3ef4c7fbc92a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -6,6 +6,7 @@ */ /* eslint-disable dot-notation */ +import type { ExperimentalFeatures } from '../../../common'; import { TelemetryEventsSender } from './sender'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; @@ -25,13 +26,13 @@ describe('TelemetryEventsSender', () => { describe('processEvents', () => { it('returns empty array when empty array is passed', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); const result = sender.processEvents([]); expect(result).toStrictEqual([]); }); it('applies the allowlist', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); const input = [ { credential_access: { @@ -465,13 +466,13 @@ describe('TelemetryEventsSender', () => { describe('queueTelemetryEvents', () => { it('queues two events', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); sender.queueTelemetryEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); expect(sender['queue'].length).toBe(2); }); it('queues more than maxQueueSize events', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); sender['maxQueueSize'] = 5; sender.queueTelemetryEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); sender.queueTelemetryEvents([{ 'event.kind': '3' }, { 'event.kind': '4' }]); @@ -481,7 +482,7 @@ describe('TelemetryEventsSender', () => { }); it('empties the queue when sending', async () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); sender['telemetryStart'] = { getIsOptedIn: jest.fn(async () => true), isOptedIn$: new Observable(), @@ -514,7 +515,7 @@ describe('TelemetryEventsSender', () => { }); it("shouldn't send when telemetry is disabled", async () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); sender['sendEvents'] = jest.fn(); const telemetryStart = { getIsOptedIn: jest.fn(async () => false), @@ -531,7 +532,7 @@ describe('TelemetryEventsSender', () => { }); it("shouldn't send when telemetry when opted in but cannot connect to elastic telemetry services", async () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); sender['sendEvents'] = jest.fn(); const telemetryStart = { getIsOptedIn: jest.fn(async () => true), @@ -558,28 +559,28 @@ describe('getV3UrlFromV2', () => { }); it('should return prod url', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); expect( sender.getV3UrlFromV2('https://telemetry.elastic.co/xpack/v2/send', 'alerts-endpoint') ).toBe('https://telemetry.elastic.co/v3/send/alerts-endpoint'); }); it('should work when receiving a V3 URL', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); expect( sender.getV3UrlFromV2('https://telemetry.elastic.co/v3/send/channel', 'alerts-endpoint') ).toBe('https://telemetry.elastic.co/v3/send/alerts-endpoint'); }); it('should return staging url', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); expect( sender.getV3UrlFromV2('https://telemetry-staging.elastic.co/xpack/v2/send', 'alerts-endpoint') ).toBe('https://telemetry-staging.elastic.co/v3-dev/send/alerts-endpoint'); }); it('should support ports and auth', () => { - const sender = new TelemetryEventsSender(logger); + const sender = new TelemetryEventsSender(logger, {} as ExperimentalFeatures); expect( sender.getV3UrlFromV2('http://user:pass@myproxy.local:1337/xpack/v2/send', 'alerts-endpoint') ).toBe('http://user:pass@myproxy.local:1337/v3/send/alerts-endpoint'); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.ts index 8e99a9e12981c..74a19afec617e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/sender.ts @@ -19,6 +19,7 @@ import type { TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; import { exhaustMap, Subject, takeUntil, timer } from 'rxjs'; +import type { ExperimentalFeatures } from '../../../common'; import type { ITelemetryReceiver } from './receiver'; import { copyAllowlistedFields, filterList } from './filterlists'; import { createTelemetryTaskConfigs } from './tasks'; @@ -99,6 +100,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { private readonly initialCheckDelayMs = 10 * 1000; private readonly checkIntervalMs = 60 * 1000; private readonly logger: TelemetryLogger; + private readonly experimentalFeatures: ExperimentalFeatures; private readonly stop$ = new Subject(); private maxQueueSize = telemetryConfiguration.telemetry_max_buffer_size; private telemetryStart?: TelemetryPluginStart; @@ -116,7 +118,8 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { private asyncTelemetrySender?: IAsyncTelemetryEventsSender; - constructor(logger: Logger) { + constructor(logger: Logger, experimentalFeatures: ExperimentalFeatures) { + this.experimentalFeatures = experimentalFeatures; this.logger = newTelemetryLogger(logger.get('telemetry_events.sender')); } @@ -131,7 +134,7 @@ export class TelemetryEventsSender implements ITelemetryEventsSender { this.telemetryUsageCounter = telemetryUsageCounter; if (taskManager) { const taskMetricsService = new TaskMetricsService(this.logger, this); - this.telemetryTasks = createTelemetryTaskConfigs().map( + this.telemetryTasks = createTelemetryTaskConfigs(this.experimentalFeatures).map( (config: SecurityTelemetryTaskConfig) => { const task = new SecurityTelemetryTask( config, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.test.ts new file mode 100644 index 0000000000000..7d7d56374f2ce --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core/server/mocks'; +import { createTelemetryCustomResponseActionRulesTaskConfig } from './custom_response_actions_rule'; +import { + createMockTelemetryEventsSender, + createMockTelemetryReceiver, + createMockTaskMetrics, +} from '../__mocks__'; + +describe('security response actions rule task test', () => { + let logger: ReturnType; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + }); + + test('security response actions rule task should fetch response actions rules data', async () => { + const testTaskExecutionPeriod = { + last: undefined, + current: new Date().toISOString(), + }; + const mockTelemetryEventsSender = createMockTelemetryEventsSender(); + const mockTelemetryReceiver = createMockTelemetryReceiver(); + const telemetryCustomResponseActionsRulesTaskConfig = + createTelemetryCustomResponseActionRulesTaskConfig(1); + const mockTaskMetrics = createMockTaskMetrics(); + + await telemetryCustomResponseActionsRulesTaskConfig.runTask( + 'test-id', + logger, + mockTelemetryReceiver, + mockTelemetryEventsSender, + mockTaskMetrics, + testTaskExecutionPeriod + ); + + expect(mockTelemetryReceiver.fetchResponseActionsRules).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.ts new file mode 100644 index 0000000000000..946f3b8813238 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/custom_response_actions_rule.ts @@ -0,0 +1,155 @@ +/* + * 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 { cloneDeep } from 'lodash'; +import type { Logger } from '@kbn/core/server'; +import { + batchTelemetryRecords, + responseActionsCustomRuleTelemetryData, + newTelemetryLogger, + createUsageCounterLabel, + safeValue, +} from '../helpers'; +import type { ITelemetryEventsSender } from '../sender'; +import type { ITelemetryReceiver } from '../receiver'; +import { + TelemetryChannel, + type ResponseActionRules, + type ResponseActionsRuleResponseAggregations, +} from '../types'; +import type { TaskExecutionPeriod } from '../task'; +import type { ITaskMetricsService } from '../task_metrics.types'; +import { telemetryConfiguration } from '../configuration'; + +export function createTelemetryCustomResponseActionRulesTaskConfig(maxTelemetryBatch: number) { + const taskName = 'Security Solution Response Actions Rules Telemetry'; + const taskType = 'security:telemetry-response-actions-rules'; + return { + type: taskType, + title: taskName, + interval: '24h', + timeout: '10m', + version: '1.0.0', + runTask: async ( + taskId: string, + logger: Logger, + receiver: ITelemetryReceiver, + sender: ITelemetryEventsSender, + taskMetricsService: ITaskMetricsService, + taskExecutionPeriod: TaskExecutionPeriod + ) => { + const mdc = { task_id: taskId, task_execution_period: taskExecutionPeriod }; + const log = newTelemetryLogger(logger.get('response_actions_rules'), mdc); + const usageCollector = sender.getTelemetryUsageCluster(); + const usageLabelEndpointPrefix: string[] = [ + 'security_telemetry', + 'endpoint-response-actions-rules', + ]; + const usageLabelOsqueryPrefix: string[] = [ + 'security_telemetry', + 'osquery-response-actions-rules', + ]; + const trace = taskMetricsService.start(taskType); + + log.l('Running response actions rules telemetry task'); + + try { + const [clusterInfoPromise, licenseInfoPromise] = await Promise.allSettled([ + receiver.fetchClusterInfo(), + receiver.fetchLicenseInfo(), + ]); + + const clusterInfo = safeValue(clusterInfoPromise); + const licenseInfo = safeValue(licenseInfoPromise); + + const { + body: { aggregations }, + } = await receiver.fetchResponseActionsRules( + taskExecutionPeriod.last ?? 'now-24h', + taskExecutionPeriod.current + ); + + if (!aggregations || !aggregations.actionTypes) { + log.debug('no custom response action rules found'); + await taskMetricsService.end(trace); + return 0; + } + + const responseActionRules = ( + aggregations as unknown as ResponseActionsRuleResponseAggregations + ).actionTypes.buckets.reduce( + (acc, agg) => { + if (agg.key === '.endpoint') { + acc.endpoint = agg.doc_count; + } else if (agg.key === '.osquery') { + acc.osquery = agg.doc_count; + } + return acc; + }, + { endpoint: 0, osquery: 0 } + ); + + const shouldNotProcessTelemetry = + responseActionRules.endpoint === 0 || responseActionRules.osquery === 0; + + if (shouldNotProcessTelemetry) { + log.debug('no new custom response action rules found'); + await taskMetricsService.end(trace); + return 0; + } + + const responseActionsRulesTelemetryData = responseActionsCustomRuleTelemetryData( + responseActionRules, + clusterInfo, + licenseInfo + ); + + log.l('Custom response actions rules data', { + data: JSON.stringify(responseActionsRulesTelemetryData), + }); + + usageCollector?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelEndpointPrefix), + counterType: 'response_actions_endpoint_rules_count', + incrementBy: responseActionsRulesTelemetryData.response_actions_rules.endpoint, + }); + + usageCollector?.incrementCounter({ + counterName: createUsageCounterLabel(usageLabelOsqueryPrefix), + counterType: 'response_actions_osquery_rules_count', + incrementBy: responseActionsRulesTelemetryData.response_actions_rules.osquery, + }); + + const documents = cloneDeep(Object.values(responseActionsRulesTelemetryData)); + + if (telemetryConfiguration.use_async_sender) { + await sender.sendAsync(TelemetryChannel.LISTS, documents); + } else { + const batches = batchTelemetryRecords(documents, maxTelemetryBatch); + for (const batch of batches) { + await sender.sendOnDemand(TelemetryChannel.LISTS, batch); + } + } + + await taskMetricsService.end(trace); + + const totalCount = Object.values( + responseActionsRulesTelemetryData.response_actions_rules + ).reduce((acc, count) => acc + count, 0); + + log.l('Response actions rules telemetry task executed', { + totalCount, + }); + + return totalCount; + } catch (err) { + await taskMetricsService.end(trace, err); + return 0; + } + }, + }; +} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/index.ts index b5496373937c2..5f2fa7f955e66 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/tasks/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { ExperimentalFeatures } from '../../../../common'; import type { SecurityTelemetryTaskConfig } from '../task'; import { createTelemetryDiagnosticsTaskConfig } from './diagnostic'; import { createTelemetryEndpointTaskConfig } from './endpoint'; @@ -18,9 +19,12 @@ import { telemetryConfiguration } from '../configuration'; import { createTelemetryFilterListArtifactTaskConfig } from './filterlists'; import { createTelemetryIndicesMetadataTaskConfig } from './indices.metadata'; import { createIngestStatsTaskConfig } from './ingest_pipelines_stats'; +import { createTelemetryCustomResponseActionRulesTaskConfig } from './custom_response_actions_rule'; -export function createTelemetryTaskConfigs(): SecurityTelemetryTaskConfig[] { - return [ +export function createTelemetryTaskConfigs( + experimentalFeatures: ExperimentalFeatures +): SecurityTelemetryTaskConfig[] { + const tasks = [ createTelemetryDiagnosticsTaskConfig(), createTelemetryEndpointTaskConfig(telemetryConfiguration.max_security_list_telemetry_batch), createTelemetrySecurityListTaskConfig(telemetryConfiguration.max_endpoint_telemetry_batch), @@ -35,4 +39,14 @@ export function createTelemetryTaskConfigs(): SecurityTelemetryTaskConfig[] { createTelemetryIndicesMetadataTaskConfig(), createIngestStatsTaskConfig(), ]; + + if (experimentalFeatures.responseActionsTelemetryEnabled) { + tasks.push( + createTelemetryCustomResponseActionRulesTaskConfig( + telemetryConfiguration.max_detection_rule_telemetry_batch + ) + ); + } + + return tasks; } diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/types.ts index e21d60c03a1a6..3995d25fce6d8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/telemetry/types.ts @@ -386,6 +386,27 @@ interface ExceptionListEntry { namespace_type: string; } +export interface ResponseActionsRuleResponseAggregations { + actionTypes: { + buckets: Array<{ + key: '.endpoint' | '.osquery'; + doc_count: number; + }>; + }; +} + +export interface ResponseActionsRuleTelemetryTemplate { + '@timestamp': string; + cluster_uuid: string; + cluster_name: string; + license_id: string | undefined; + response_actions_rules: ResponseActionRules; +} + +export interface ResponseActionRules { + endpoint: number; + osquery: number; +} interface DetectionRuleParms { ruleId: string; version: number; diff --git a/x-pack/solutions/security/plugins/security_solution/server/plugin.ts b/x-pack/solutions/security/plugins/security_solution/server/plugin.ts index 54c88e914f5a5..b13498debf7d3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/plugin.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/plugin.ts @@ -179,7 +179,10 @@ export class Plugin implements ISecuritySolutionPlugin { ); this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger); - this.telemetryEventsSender = new TelemetryEventsSender(this.logger); + this.telemetryEventsSender = new TelemetryEventsSender( + this.logger, + this.config.experimentalFeatures + ); this.asyncTelemetryEventsSender = new AsyncTelemetryEventsSender(this.logger); this.telemetryReceiver = new TelemetryReceiver(this.logger);