Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Implement normalization of ruleSource for API responses #188631

Merged
merged 6 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
is_customized: false,
});
});

it('should return snake_case version of rule_source 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,
is_customized: externalRuleSource.isCustomized,
});

const internalRuleSource: BaseRuleParams['ruleSource'] = {
type: 'internal',
};
const internalResult = normalizeRuleSource({
immutable: false,
ruleSource: internalRuleSource,
});
expect(internalResult).toEqual({
type: internalRuleSource.type,
});
});
});
Original file line number Diff line number Diff line change
@@ -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,
}),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down