mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] Implement normalization of ruleSource for API responses (#188631)
Fixes: https://github.com/elastic/kibana/issues/180140 ## Summary - Implements normalization of`rule_source` for API responses - `rule_source` field in API responses is calculated out of the `immutable` and `ruleSource` fields. ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
013276edac
commit
232a16637d
9 changed files with 129 additions and 73 deletions
|
@ -47,6 +47,9 @@ export const getOutputRuleAlertForRest = (): RuleResponse => ({
|
|||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
interval: '5m',
|
||||
risk_score: 50,
|
||||
|
|
|
@ -76,6 +76,9 @@ const reported = {
|
|||
from: 'now-6m',
|
||||
id: 'rule-id',
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
investigation_fields: undefined,
|
||||
language: 'kuery',
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from '../../../normalization/rule_actions';
|
||||
import { typeSpecificCamelToSnake } from './type_specific_camel_to_snake';
|
||||
import { commonParamsCamelToSnake } from './common_params_camel_to_snake';
|
||||
import { normalizeRuleParams } from './normalize_rule_params';
|
||||
|
||||
export const internalRuleToAPIResponse = (
|
||||
rule: SanitizedRule<RuleParams> | ResolvedSanitizedRule<RuleParams>
|
||||
|
@ -31,6 +32,7 @@ export const internalRuleToAPIResponse = (
|
|||
const alertActions = rule.actions.map(transformAlertToRuleAction);
|
||||
const throttle = transformFromAlertThrottle(rule);
|
||||
const actions = transformToActionFrequency(alertActions, throttle);
|
||||
const normalizedRuleParams = normalizeRuleParams(rule.params);
|
||||
|
||||
return {
|
||||
// saved object properties
|
||||
|
@ -49,7 +51,7 @@ export const internalRuleToAPIResponse = (
|
|||
enabled: rule.enabled,
|
||||
revision: rule.revision,
|
||||
// Security solution shared rule params
|
||||
...commonParamsCamelToSnake(rule.params),
|
||||
...commonParamsCamelToSnake(normalizedRuleParams),
|
||||
// Type specific security solution rule params
|
||||
...typeSpecificCamelToSnake(rule.params),
|
||||
// Actions
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { normalizeRuleSource } from './normalize_rule_params';
|
||||
import type { BaseRuleParams } from '../../../../rule_schema';
|
||||
|
||||
describe('normalizeRuleSource', () => {
|
||||
it('should return rule_source of type `internal` when immutable is false and ruleSource is undefined', () => {
|
||||
const result = normalizeRuleSource({
|
||||
immutable: false,
|
||||
ruleSource: undefined,
|
||||
});
|
||||
expect(result).toEqual({
|
||||
type: 'internal',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return rule_source of type `external` and `isCustomized: false` when immutable is true and ruleSource is undefined', () => {
|
||||
const result = normalizeRuleSource({
|
||||
immutable: true,
|
||||
ruleSource: undefined,
|
||||
});
|
||||
expect(result).toEqual({
|
||||
type: 'external',
|
||||
isCustomized: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return existing value when ruleSource is present', () => {
|
||||
const externalRuleSource: BaseRuleParams['ruleSource'] = {
|
||||
type: 'external',
|
||||
isCustomized: true,
|
||||
};
|
||||
const externalResult = normalizeRuleSource({ immutable: true, ruleSource: externalRuleSource });
|
||||
expect(externalResult).toEqual({
|
||||
type: externalRuleSource.type,
|
||||
isCustomized: externalRuleSource.isCustomized,
|
||||
});
|
||||
|
||||
const internalRuleSource: BaseRuleParams['ruleSource'] = {
|
||||
type: 'internal',
|
||||
};
|
||||
const internalResult = normalizeRuleSource({
|
||||
immutable: false,
|
||||
ruleSource: internalRuleSource,
|
||||
});
|
||||
expect(internalResult).toEqual({
|
||||
type: internalRuleSource.type,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { BaseRuleParams, RuleSourceCamelCased } from '../../../../rule_schema';
|
||||
|
||||
interface NormalizeRuleSourceParams {
|
||||
immutable: BaseRuleParams['immutable'];
|
||||
ruleSource: BaseRuleParams['ruleSource'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Since there's no mechanism to migrate all rules at the same time,
|
||||
* we cannot guarantee that the ruleSource params is present in all rules.
|
||||
* This function will normalize the ruleSource param, creating it if does
|
||||
* not exist in ES, based on the immutable param.
|
||||
*/
|
||||
export const normalizeRuleSource = ({
|
||||
immutable,
|
||||
ruleSource,
|
||||
}: NormalizeRuleSourceParams): RuleSourceCamelCased => {
|
||||
if (!ruleSource) {
|
||||
const normalizedRuleSource: RuleSourceCamelCased = immutable
|
||||
? {
|
||||
type: 'external',
|
||||
isCustomized: false,
|
||||
}
|
||||
: {
|
||||
type: 'internal',
|
||||
};
|
||||
|
||||
return normalizedRuleSource;
|
||||
}
|
||||
return ruleSource;
|
||||
};
|
||||
|
||||
export const normalizeRuleParams = (params: BaseRuleParams) => {
|
||||
return {
|
||||
...params,
|
||||
// Fields to normalize
|
||||
ruleSource: normalizeRuleSource({
|
||||
immutable: params.immutable,
|
||||
ruleSource: params.ruleSource,
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -100,6 +100,9 @@ describe('getExportAll', () => {
|
|||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
interval: '5m',
|
||||
rule_id: 'rule-1',
|
||||
|
@ -280,6 +283,9 @@ describe('getExportAll', () => {
|
|||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
interval: '5m',
|
||||
rule_id: 'rule-1',
|
||||
|
|
|
@ -8,85 +8,15 @@
|
|||
import { transformValidateBulkError } from './validate';
|
||||
import type { BulkError } from '../../routes/utils';
|
||||
import { getRuleMock } from '../../routes/__mocks__/request_responses';
|
||||
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
|
||||
import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock';
|
||||
import { getQueryRuleParams } from '../../rule_schema/mocks';
|
||||
import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema';
|
||||
|
||||
export const ruleOutput = (): RuleResponse => ({
|
||||
actions: [],
|
||||
author: ['Elastic'],
|
||||
building_block_type: 'default',
|
||||
created_at: '2019-12-13T16:40:33.400Z',
|
||||
updated_at: '2019-12-13T16:40:33.400Z',
|
||||
created_by: 'elastic',
|
||||
description: 'Detecting root and admin users',
|
||||
enabled: true,
|
||||
false_positives: [],
|
||||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
interval: '5m',
|
||||
rule_id: 'rule-1',
|
||||
language: 'kuery',
|
||||
license: 'Elastic License',
|
||||
output_index: '.siem-signals',
|
||||
max_signals: 10000,
|
||||
risk_score: 50,
|
||||
risk_score_mapping: [],
|
||||
name: 'Detect Root/Admin Users',
|
||||
query: 'user.name: root or user.name: admin',
|
||||
references: ['http://example.com', 'https://example.com'],
|
||||
severity: 'high',
|
||||
severity_mapping: [],
|
||||
updated_by: 'elastic',
|
||||
tags: [],
|
||||
to: 'now',
|
||||
type: 'query',
|
||||
throttle: undefined,
|
||||
threat: getThreatMock(),
|
||||
version: 1,
|
||||
revision: 0,
|
||||
filters: [
|
||||
{
|
||||
query: {
|
||||
match_phrase: {
|
||||
'host.name': 'some-host',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
exceptions_list: getListArrayMock(),
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
meta: {
|
||||
someMeta: 'someField',
|
||||
},
|
||||
note: '# Investigative notes',
|
||||
timeline_title: 'some-timeline-title',
|
||||
timeline_id: 'some-timeline-id',
|
||||
related_integrations: [],
|
||||
required_fields: [],
|
||||
response_actions: undefined,
|
||||
setup: '',
|
||||
outcome: undefined,
|
||||
alias_target_id: undefined,
|
||||
alias_purpose: undefined,
|
||||
rule_name_override: undefined,
|
||||
timestamp_override: undefined,
|
||||
timestamp_override_fallback_disabled: undefined,
|
||||
namespace: undefined,
|
||||
data_view_id: undefined,
|
||||
saved_id: undefined,
|
||||
alert_suppression: undefined,
|
||||
investigation_fields: undefined,
|
||||
});
|
||||
import { getOutputRuleAlertForRest } from '../../routes/__mocks__/utils';
|
||||
|
||||
describe('validate', () => {
|
||||
describe('transformValidateBulkError', () => {
|
||||
test('it should do a validation correctly of a rule id', () => {
|
||||
const ruleAlert = getRuleMock(getQueryRuleParams());
|
||||
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert);
|
||||
expect(validatedOrError).toEqual(ruleOutput());
|
||||
expect(validatedOrError).toEqual(getOutputRuleAlertForRest());
|
||||
});
|
||||
|
||||
test('it should do an in-validation correctly of a rule id', () => {
|
||||
|
|
|
@ -32,6 +32,9 @@ export const getBaseRuleParams = (): BaseRuleParams => {
|
|||
description: 'Detecting root and admin users',
|
||||
falsePositives: [],
|
||||
immutable: false,
|
||||
ruleSource: {
|
||||
type: 'internal',
|
||||
},
|
||||
from: 'now-6m',
|
||||
to: 'now',
|
||||
severity: 'high',
|
||||
|
|
|
@ -162,6 +162,9 @@ describe('buildAlert', () => {
|
|||
},
|
||||
],
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
type: 'query',
|
||||
language: 'kuery',
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
|
@ -357,6 +360,9 @@ describe('buildAlert', () => {
|
|||
},
|
||||
],
|
||||
immutable: false,
|
||||
rule_source: {
|
||||
type: 'internal',
|
||||
},
|
||||
type: 'query',
|
||||
language: 'kuery',
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue