mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solutions] Fixes telemetry to work with rule types (#120809)
## Summary What this does: * Fixes telemetry to work with the newer rule types * Updates the queries to the new rule types and rule query names * Uses constants where I can from cases and the new rule types * Changes the index to the new index type alias * Adds e2e backend tests we didn't have before What this does not do: * Doesn't add e2e backend tests for alerts added to cases * Doesn't add e2e backend tests for ML jobs for security_solution Those two test scenarios have to be manually tested still. Manual testing: To see telemetry go to advanced settings -> Usage Data (and click cluster data): <img width="2193" alt="Screen Shot 2021-12-08 at 4 14 43 PM" src="https://user-images.githubusercontent.com/1151048/145310671-b7350892-d290-4f3d-ab8c-4e9ec86f4120.png"> Create alerts of different types and add them to cases: <img width="1464" alt="Screen Shot 2021-12-08 at 4 48 21 PM" src="https://user-images.githubusercontent.com/1151048/145310800-2fae6373-5e84-46ec-9e44-f7a140ea9c36.png"> Activate ML_jobs and any alerts that have ML jobs associated: <img width="754" alt="Screen Shot 2021-12-08 at 5 08 42 PM" src="https://user-images.githubusercontent.com/1151048/145310978-861f4bb7-2575-4a07-a55f-1e4fdfe288e7.png"> When clicking advanced settings -> Usage Data -> Click cluster data Search for `security_solution` and then ensure that the data looks as expected underneath the different values such as: `ml_jobs` <img width="750" alt="Screen Shot 2021-12-08 at 3 08 25 PM" src="https://user-images.githubusercontent.com/1151048/145311124-c3523d4e-b31b-4bab-b14e-267155bf2b92.png"> `detection_rules` and `cases` working again: <img width="420" alt="Screen Shot 2021-12-08 at 4 43 10 PM" src="https://user-images.githubusercontent.com/1151048/145311192-e062c435-e8c3-4919-b4e9-8a786dc588c6.png"> Note, `detection_rule_detail` will only be filled in if have prepackaged rules installed: <img width="761" alt="Screen Shot 2021-12-08 at 5 14 50 PM" src="https://user-images.githubusercontent.com/1151048/145311446-1d78541f-1211-4389-b947-7c0939d7c946.png"> Also note that the `detection_rule_detail`'s `rule_id` is its UUID and not its `rule_id`. That's the way it's been in the codebase for a while it looks like so I have not changed that behavior. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
5e34758d43
commit
181b9e0271
12 changed files with 718 additions and 13 deletions
|
@ -45,7 +45,12 @@ import { initSavedObjects } from './saved_objects';
|
|||
import { AppClientFactory } from './client';
|
||||
import { createConfig, ConfigType } from './config';
|
||||
import { initUiSettings } from './ui_settings';
|
||||
import { APP_ID, SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../common/constants';
|
||||
import {
|
||||
APP_ID,
|
||||
SERVER_APP_ID,
|
||||
LEGACY_NOTIFICATIONS_ID,
|
||||
DEFAULT_ALERTS_INDEX,
|
||||
} from '../common/constants';
|
||||
import { registerEndpointRoutes } from './endpoint/routes/metadata';
|
||||
import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency';
|
||||
import { registerResolverRoutes } from './endpoint/routes/resolver';
|
||||
|
@ -161,7 +166,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
initUsageCollectors({
|
||||
core,
|
||||
kibanaIndex: core.savedObjects.getKibanaIndex(),
|
||||
signalsIndex: config.signalsIndex,
|
||||
signalsIndex: DEFAULT_ALERTS_INDEX,
|
||||
ml: plugins.ml,
|
||||
usageCollection: plugins.usageCollection,
|
||||
});
|
||||
|
|
|
@ -5,7 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SIGNALS_ID } from '@kbn/securitysolution-rules';
|
||||
import {
|
||||
SIGNALS_ID,
|
||||
EQL_RULE_TYPE_ID,
|
||||
INDICATOR_RULE_TYPE_ID,
|
||||
ML_RULE_TYPE_ID,
|
||||
QUERY_RULE_TYPE_ID,
|
||||
THRESHOLD_RULE_TYPE_ID,
|
||||
SAVED_QUERY_RULE_TYPE_ID,
|
||||
} from '@kbn/securitysolution-rules';
|
||||
import { ALERT_RULE_UUID } from '@kbn/rule-data-utils';
|
||||
import { CASE_COMMENT_SAVED_OBJECT } from '../../../../cases/common/constants';
|
||||
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from '../../../../../../src/core/server';
|
||||
import { isElasticRule } from './index';
|
||||
|
@ -188,7 +198,25 @@ export const getDetectionRuleMetrics = async (
|
|||
): Promise<DetectionRuleAdoption> => {
|
||||
let rulesUsage: DetectionRulesTypeUsage = initialDetectionRulesUsage;
|
||||
const ruleSearchOptions: RuleSearchParams = {
|
||||
body: { query: { bool: { filter: { term: { 'alert.alertTypeId': SIGNALS_ID } } } } },
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: {
|
||||
terms: {
|
||||
'alert.alertTypeId': [
|
||||
SIGNALS_ID,
|
||||
EQL_RULE_TYPE_ID,
|
||||
ML_RULE_TYPE_ID,
|
||||
QUERY_RULE_TYPE_ID,
|
||||
SAVED_QUERY_RULE_TYPE_ID,
|
||||
INDICATOR_RULE_TYPE_ID,
|
||||
THRESHOLD_RULE_TYPE_ID,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
filter_path: [],
|
||||
ignore_unavailable: true,
|
||||
index: kibanaIndex,
|
||||
|
@ -203,7 +231,7 @@ export const getDetectionRuleMetrics = async (
|
|||
body: {
|
||||
aggs: {
|
||||
detectionAlerts: {
|
||||
terms: { field: 'signal.rule.id.keyword' },
|
||||
terms: { field: ALERT_RULE_UUID },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
|
@ -224,11 +252,10 @@ export const getDetectionRuleMetrics = async (
|
|||
})) as { body: AlertsAggregationResponse };
|
||||
|
||||
const cases = await savedObjectClient.find<CasesSavedObject>({
|
||||
type: 'cases-comments',
|
||||
fields: [],
|
||||
type: CASE_COMMENT_SAVED_OBJECT,
|
||||
page: 1,
|
||||
perPage: MAX_RESULTS_WINDOW,
|
||||
filter: 'cases-comments.attributes.type: alert',
|
||||
filter: `${CASE_COMMENT_SAVED_OBJECT}.attributes.type: alert`,
|
||||
});
|
||||
|
||||
const casesCache = cases.saved_objects.reduce((cache, { attributes: casesObject }) => {
|
||||
|
@ -247,14 +274,13 @@ export const getDetectionRuleMetrics = async (
|
|||
|
||||
const alertsCache = new Map<string, number>();
|
||||
alertBuckets.map((bucket) => alertsCache.set(bucket.key, bucket.doc_count));
|
||||
|
||||
if (ruleResults.hits?.hits?.length > 0) {
|
||||
const ruleObjects = ruleResults.hits.hits.map((hit) => {
|
||||
const ruleId = hit._id.split(':')[1];
|
||||
const isElastic = isElasticRule(hit._source?.alert.tags);
|
||||
return {
|
||||
rule_name: hit._source?.alert.name,
|
||||
rule_id: ruleId,
|
||||
rule_id: hit._source?.alert.params.ruleId,
|
||||
rule_type: hit._source?.alert.params.type,
|
||||
rule_version: hit._source?.alert.params.version,
|
||||
enabled: hit._source?.alert.enabled,
|
||||
|
|
|
@ -111,7 +111,7 @@ describe('Detections Usage and Metrics', () => {
|
|||
created_on: '2021-03-23T17:15:59.634Z',
|
||||
elastic_rule: true,
|
||||
enabled: false,
|
||||
rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d',
|
||||
rule_id: '5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de',
|
||||
rule_name: 'Azure Diagnostic Settings Deletion',
|
||||
rule_type: 'query',
|
||||
rule_version: 4,
|
||||
|
@ -248,7 +248,7 @@ describe('Detections Usage and Metrics', () => {
|
|||
created_on: '2021-03-23T17:15:59.634Z',
|
||||
elastic_rule: true,
|
||||
enabled: false,
|
||||
rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d',
|
||||
rule_id: '5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de',
|
||||
rule_name: 'Azure Diagnostic Settings Deletion',
|
||||
rule_type: 'query',
|
||||
rule_version: 4,
|
||||
|
|
|
@ -9,7 +9,7 @@ interface RuleSearchBody {
|
|||
query: {
|
||||
bool: {
|
||||
filter: {
|
||||
term: { [key: string]: string };
|
||||
terms: { [key: string]: string[] };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -68,5 +68,9 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
describe('', function () {
|
||||
loadTestFile(require.resolve('./alerts/index'));
|
||||
});
|
||||
|
||||
describe('', function () {
|
||||
loadTestFile(require.resolve('./telemetry/index'));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
These are tests for the telemetry rules within "security_solution/server/usage"
|
||||
* detection_rules
|
||||
|
|
@ -0,0 +1,426 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { DetectionMetrics } from '../../../../../plugins/security_solution/server/usage/detections/types';
|
||||
import {
|
||||
EqlCreateSchema,
|
||||
QueryCreateSchema,
|
||||
ThreatMatchCreateSchema,
|
||||
ThresholdCreateSchema,
|
||||
} from '../../../../../plugins/security_solution/common/detection_engine/schemas/request';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import {
|
||||
createRule,
|
||||
createSignalsIndex,
|
||||
deleteAllAlerts,
|
||||
deleteSignalsIndex,
|
||||
getEqlRuleForSignalTesting,
|
||||
getInitialDetectionMetrics,
|
||||
getRuleForSignalTesting,
|
||||
getSimpleMlRule,
|
||||
getSimpleThreatMatch,
|
||||
getStats,
|
||||
getThresholdRuleForSignalTesting,
|
||||
installPrePackagedRules,
|
||||
waitForRuleSuccessOrStatus,
|
||||
waitForSignalsToBePresent,
|
||||
} from '../../../utils';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
|
||||
describe('Detection rule telemetry', async () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/telemetry');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/telemetry');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await createSignalsIndex(supertest, log);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteSignalsIndex(supertest, log);
|
||||
await deleteAllAlerts(supertest, log);
|
||||
});
|
||||
|
||||
it('should have initialized empty/zero values when no rules are running', async () => {
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
expect(stats).to.eql(getInitialDetectionMetrics());
|
||||
});
|
||||
});
|
||||
|
||||
describe('"kql" rule type', () => {
|
||||
it('should show stats for active rule', async () => {
|
||||
const rule: QueryCreateSchema = getRuleForSignalTesting(['telemetry']);
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccessOrStatus(supertest, log, id);
|
||||
await waitForSignalsToBePresent(supertest, log, 4, [id]);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
query: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for in-active rule', async () => {
|
||||
const rule: QueryCreateSchema = getRuleForSignalTesting(['telemetry'], 'rule-1', false);
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
query: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
disabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
disabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"eql" rule type', () => {
|
||||
it('should show stats for active rule', async () => {
|
||||
const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['telemetry']);
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccessOrStatus(supertest, log, id);
|
||||
await waitForSignalsToBePresent(supertest, log, 4, [id]);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
eql: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for in-active rule', async () => {
|
||||
const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['telemetry'], 'rule-1', false);
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
eql: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
disabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
disabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"threshold" rule type', () => {
|
||||
it('should show stats for active rule', async () => {
|
||||
const rule: ThresholdCreateSchema = {
|
||||
...getThresholdRuleForSignalTesting(['telemetry']),
|
||||
threshold: {
|
||||
field: 'keyword',
|
||||
value: 1,
|
||||
},
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccessOrStatus(supertest, log, id);
|
||||
await waitForSignalsToBePresent(supertest, log, 4, [id]);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
threshold: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for in-active rule', async () => {
|
||||
const rule: ThresholdCreateSchema = {
|
||||
...getThresholdRuleForSignalTesting(['telemetry'], 'rule-1', false),
|
||||
threshold: {
|
||||
field: 'keyword',
|
||||
value: 1,
|
||||
},
|
||||
};
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
threshold: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
disabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
disabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"ml" rule type', () => {
|
||||
// Note: We don't actually find signals with this test as we don't have a good way of signal finding with ML rules.
|
||||
it('should show stats for active rule', async () => {
|
||||
const rule = getSimpleMlRule('rule-1', true);
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
machine_learning: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
enabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
enabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for in-active rule', async () => {
|
||||
const rule = getSimpleMlRule();
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
machine_learning: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
disabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
disabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"indicator_match/threat_match" rule type', () => {
|
||||
it('should show stats for active rule', async () => {
|
||||
const rule: ThreatMatchCreateSchema = {
|
||||
...getSimpleThreatMatch('rule-1', true),
|
||||
index: ['telemetry'],
|
||||
threat_index: ['telemetry'],
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'keyword',
|
||||
value: 'keyword',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
await waitForRuleSuccessOrStatus(supertest, log, id);
|
||||
await waitForSignalsToBePresent(supertest, log, 4, [id]);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
threat_match: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
enabled: 1,
|
||||
alerts: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for in-active rule', async () => {
|
||||
const rule = getSimpleThreatMatch();
|
||||
await createRule(supertest, log, rule);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
const expected: DetectionMetrics = {
|
||||
...getInitialDetectionMetrics(),
|
||||
detection_rules: {
|
||||
...getInitialDetectionMetrics().detection_rules,
|
||||
detection_rule_usage: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage,
|
||||
threat_match: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query,
|
||||
disabled: 1,
|
||||
},
|
||||
custom_total: {
|
||||
...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total,
|
||||
disabled: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(stats).to.eql(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"pre-packaged" rules', async () => {
|
||||
it('should show stats for totals for in-active pre-packaged rules', async () => {
|
||||
await installPrePackagedRules(supertest, log);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
expect(stats.detection_rules.detection_rule_usage.elastic_total.enabled).above(0);
|
||||
expect(stats.detection_rules.detection_rule_usage.elastic_total.disabled).above(0);
|
||||
expect(stats.detection_rules.detection_rule_usage.elastic_total.enabled).above(0);
|
||||
expect(stats.detection_rules.detection_rule_usage.custom_total.enabled).equal(0);
|
||||
expect(stats.detection_rules.detection_rule_detail.length).above(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show stats for the detection_rule_details for pre-packaged rules', async () => {
|
||||
await installPrePackagedRules(supertest, log);
|
||||
await retry.try(async () => {
|
||||
const stats = await getStats(supertest, log);
|
||||
|
||||
// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file:
|
||||
// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json
|
||||
// We have to search by "rule_name" since the "rule_id" it is storing is the Saved Object ID and not the rule_id
|
||||
const foundRule = stats.detection_rules.detection_rule_detail.find(
|
||||
(rule) => rule.rule_id === '9a1a2dae-0b5f-4c3d-8305-a268d404c306'
|
||||
);
|
||||
if (foundRule == null) {
|
||||
throw new Error('Found rule should not be null');
|
||||
}
|
||||
const {
|
||||
created_on: createdOn,
|
||||
updated_on: updatedOn,
|
||||
rule_id: ruleId,
|
||||
...omittedFields
|
||||
} = foundRule;
|
||||
expect(omittedFields).to.eql({
|
||||
rule_name: 'Endpoint Security',
|
||||
rule_type: 'query',
|
||||
rule_version: 3,
|
||||
enabled: true,
|
||||
elastic_rule: true,
|
||||
alert_count_daily: 0,
|
||||
cases_count_total: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ loadTestFile }: FtrProviderContext): void => {
|
||||
describe('Detection rule type telemetry', function () {
|
||||
describe('', function () {
|
||||
this.tags('ciGroup11');
|
||||
loadTestFile(require.resolve('./detection_rules'));
|
||||
});
|
||||
});
|
||||
};
|
|
@ -34,6 +34,7 @@ import {
|
|||
EqlCreateSchema,
|
||||
ThresholdCreateSchema,
|
||||
PreviewRulesSchema,
|
||||
ThreatMatchCreateSchema,
|
||||
} from '../../plugins/security_solution/common/detection_engine/schemas/request';
|
||||
import { signalsMigrationType } from '../../plugins/security_solution/server/lib/detection_engine/migrations/saved_objects';
|
||||
import {
|
||||
|
@ -53,6 +54,7 @@ import {
|
|||
UPDATE_OR_CREATE_LEGACY_ACTIONS,
|
||||
} from '../../plugins/security_solution/common/constants';
|
||||
import { RACAlert } from '../../plugins/security_solution/server/lib/detection_engine/rule_types/types';
|
||||
import { DetectionMetrics } from '../../plugins/security_solution/server/usage/detections/types';
|
||||
|
||||
/**
|
||||
* This will remove server generated properties such as date times, etc...
|
||||
|
@ -1827,3 +1829,151 @@ export const getOpenSignals = async (
|
|||
await refreshIndex(es, '.alerts-security.alerts-default*');
|
||||
return getSignalsByIds(supertest, log, [rule.id]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Cluster stats URL. Replace this with any from kibana core if there is ever a constant there for this.
|
||||
*/
|
||||
export const getStatsUrl = (): string => '/api/telemetry/v2/clusters/_stats';
|
||||
|
||||
/**
|
||||
* Initial detection metrics initialized.
|
||||
*/
|
||||
export const getInitialDetectionMetrics = (): DetectionMetrics => ({
|
||||
ml_jobs: {
|
||||
ml_job_usage: {
|
||||
custom: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
},
|
||||
elastic: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
},
|
||||
},
|
||||
ml_job_metrics: [],
|
||||
},
|
||||
detection_rules: {
|
||||
detection_rule_detail: [],
|
||||
detection_rule_usage: {
|
||||
query: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
threshold: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
eql: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
machine_learning: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
threat_match: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
elastic_total: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
custom_total: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
alerts: 0,
|
||||
cases: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Given a body this will return the detection metrics from it.
|
||||
* @param body The Stats body
|
||||
* @returns Detection metrics
|
||||
*/
|
||||
export const getDetectionMetricsFromBody = (
|
||||
body: Array<{
|
||||
stats: {
|
||||
stack_stats: {
|
||||
kibana: { plugins: { security_solution: { detectionMetrics: DetectionMetrics } } };
|
||||
};
|
||||
};
|
||||
}>
|
||||
): DetectionMetrics => {
|
||||
return body[0].stats.stack_stats.kibana.plugins.security_solution.detectionMetrics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the stats from the stats endpoint
|
||||
* @param supertest The supertest agent.
|
||||
* @returns The detection metrics
|
||||
*/
|
||||
export const getStats = async (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>,
|
||||
log: ToolingLog
|
||||
): Promise<DetectionMetrics> => {
|
||||
const response = await supertest
|
||||
.post(getStatsUrl())
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ unencrypted: true });
|
||||
if (response.status !== 200) {
|
||||
log.error(
|
||||
`Did not get an expected 200 "ok" when getting the stats for detections. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify(
|
||||
response.body
|
||||
)}, status: ${JSON.stringify(response.status)}`
|
||||
);
|
||||
}
|
||||
return getDetectionMetricsFromBody(response.body);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a typical simple indicator match/threat match for testing that is easy for most basic testing
|
||||
* @param ruleId
|
||||
* @param enabled Enables the rule on creation or not. Defaulted to false.
|
||||
*/
|
||||
export const getSimpleThreatMatch = (
|
||||
ruleId = 'rule-1',
|
||||
enabled = false
|
||||
): ThreatMatchCreateSchema => ({
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
enabled,
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: ruleId,
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: '*:*',
|
||||
threat_index: ['auditbeat-*'],
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
});
|
||||
|
|
|
@ -9,3 +9,5 @@ or
|
|||
```
|
||||
x-pack/test/api_integration/apis/security_solution
|
||||
```
|
||||
|
||||
* Folder `telemetry` is for the tests underneath `detection_engine_api_integration/security_and_spaces/tests/telemetry`.
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "1",
|
||||
"index": "telemetry",
|
||||
"source": {
|
||||
"@timestamp": "2020-10-28T05:00:53.000Z",
|
||||
"keyword": "word one"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "2",
|
||||
"index": "telemetry",
|
||||
"source": {
|
||||
"@timestamp": "2020-10-28T05:01:53.000Z",
|
||||
"keyword": "word two"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "3",
|
||||
"index": "telemetry",
|
||||
"source": {
|
||||
"@timestamp": "2020-10-28T05:02:53.000Z",
|
||||
"keyword": "word three"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "4",
|
||||
"index": "telemetry",
|
||||
"source": {
|
||||
"@timestamp": "2020-10-28T05:03:53.000Z",
|
||||
"keyword": "word four"
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"index": "telemetry",
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"@timestamp": {
|
||||
"type": "date"
|
||||
},
|
||||
"keyword": { "type": "keyword" }
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_replicas": "1",
|
||||
"number_of_shards": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue