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] Adds field mapping support to rule creation Part I #70288

Merged
merged 8 commits into from Jul 2, 2020
Expand Up @@ -13,6 +13,18 @@ import { IsoDateString } from '../types/iso_date_string';
import { PositiveIntegerGreaterThanZero } from '../types/positive_integer_greater_than_zero';
import { PositiveInteger } from '../types/positive_integer';

export const author = t.array(t.string);
export type Author = t.TypeOf<typeof author>;

export const authorOrUndefined = t.union([author, t.undefined]);
export type AuthorOrUndefined = t.TypeOf<typeof authorOrUndefined>;

export const building_block_type = t.string;
export type BuildingBlockType = t.TypeOf<typeof building_block_type>;

export const buildingBlockTypeOrUndefined = t.union([building_block_type, t.undefined]);
export type BuildingBlockTypeOrUndefined = t.TypeOf<typeof buildingBlockTypeOrUndefined>;

export const description = t.string;
export type Description = t.TypeOf<typeof description>;

Expand Down Expand Up @@ -111,6 +123,12 @@ export type Language = t.TypeOf<typeof language>;
export const languageOrUndefined = t.union([language, t.undefined]);
export type LanguageOrUndefined = t.TypeOf<typeof languageOrUndefined>;

export const license = t.string;
export type License = t.TypeOf<typeof license>;

export const licenseOrUndefined = t.union([license, t.undefined]);
export type LicenseOrUndefined = t.TypeOf<typeof licenseOrUndefined>;

export const objects = t.array(t.type({ rule_id }));

export const output_index = t.string;
Expand All @@ -137,6 +155,12 @@ export type TimelineTitle = t.TypeOf<typeof t.string>;
export const timelineTitleOrUndefined = t.union([timeline_title, t.undefined]);
export type TimelineTitleOrUndefined = t.TypeOf<typeof timelineTitleOrUndefined>;

export const timestamp_override = t.string;
export type TimestampOverride = t.TypeOf<typeof timestamp_override>;

export const timestampOverrideOrUndefined = t.union([timestamp_override, t.undefined]);
export type TimestampOverrideOrUndefined = t.TypeOf<typeof timestampOverrideOrUndefined>;

export const throttle = t.string;
export type Throttle = t.TypeOf<typeof throttle>;

Expand Down Expand Up @@ -179,18 +203,65 @@ export type Name = t.TypeOf<typeof name>;
export const nameOrUndefined = t.union([name, t.undefined]);
export type NameOrUndefined = t.TypeOf<typeof nameOrUndefined>;

export const operator = t.keyof({
equals: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can use maybe: OperatorEnum['EQUALS']: null here. That would reduce one area of manual typing.

});
export type Operator = t.TypeOf<typeof operator>;
export enum OperatorEnum {
EQUALS = 'equals',
}

export const risk_score = RiskScore;
export type RiskScore = t.TypeOf<typeof risk_score>;

export const riskScoreOrUndefined = t.union([risk_score, t.undefined]);
export type RiskScoreOrUndefined = t.TypeOf<typeof riskScoreOrUndefined>;

export const risk_score_mapping_field = t.string;
export const risk_score_mapping_value = t.string;
export const risk_score_mapping_item = t.exact(
t.type({
field: risk_score_mapping_field,
operator,
value: risk_score_mapping_value,
})
);

export const risk_score_mapping = t.array(risk_score_mapping_item);
export type RiskScoreMapping = t.TypeOf<typeof risk_score_mapping>;

export const riskScoreMappingOrUndefined = t.union([risk_score_mapping, t.undefined]);
export type RiskScoreMappingOrUndefined = t.TypeOf<typeof riskScoreMappingOrUndefined>;

export const rule_name_override = t.string;
export type RuleNameOverride = t.TypeOf<typeof rule_name_override>;

export const ruleNameOverrideOrUndefined = t.union([rule_name_override, t.undefined]);
export type RuleNameOverrideOrUndefined = t.TypeOf<typeof ruleNameOverrideOrUndefined>;

export const severity = t.keyof({ low: null, medium: null, high: null, critical: null });
export type Severity = t.TypeOf<typeof severity>;

export const severityOrUndefined = t.union([severity, t.undefined]);
export type SeverityOrUndefined = t.TypeOf<typeof severityOrUndefined>;

export const severity_mapping_field = t.string;
export const severity_mapping_value = t.string;
export const severity_mapping_item = t.exact(
t.type({
field: severity_mapping_field,
operator,
value: severity_mapping_value,
severity,
})
);

export const severity_mapping = t.array(severity_mapping_item);
export type SeverityMapping = t.TypeOf<typeof severity_mapping>;

export const severityMappingOrUndefined = t.union([severity_mapping, t.undefined]);
export type SeverityMappingOrUndefined = t.TypeOf<typeof severityMappingOrUndefined>;

export const status = t.keyof({ open: null, closed: null, 'in-progress': null });
export type Status = t.TypeOf<typeof status>;

Expand Down
Expand Up @@ -23,12 +23,15 @@ export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema =>
});

export const getAddPrepackagedRulesSchemaDecodedMock = (): AddPrepackagedRulesSchemaDecoded => ({
author: [],
description: 'some description',
name: 'Query with a rule id',
query: 'user.name: root or user.name: admin',
severity: 'high',
severity_mapping: [],
type: 'query',
risk_score: 55,
risk_score_mapping: [],
language: 'kuery',
references: [],
actions: [],
Expand Down
Expand Up @@ -37,6 +37,13 @@ import {
query,
rule_id,
version,
building_block_type,
license,
rule_name_override,
timestamp_override,
Author,
RiskScoreMapping,
SeverityMapping,
} from '../common/schemas';
/* eslint-enable @typescript-eslint/camelcase */

Expand All @@ -52,6 +59,8 @@ import {
DefaultThrottleNull,
DefaultListArray,
ListArray,
DefaultRiskScoreMappingArray,
DefaultSeverityMappingArray,
} from '../types';

/**
Expand Down Expand Up @@ -79,6 +88,8 @@ export const addPrepackagedRulesSchema = t.intersection([
t.partial({
actions: DefaultActionsArray, // defaults to empty actions array if not set during decode
anomaly_threshold, // defaults to undefined if not set during decode
author: DefaultStringArray, // defaults to empty array of strings if not set during decode
building_block_type, // defaults to undefined if not set during decode
enabled: DefaultBooleanFalse, // defaults to false if not set during decode
false_positives: DefaultStringArray, // defaults to empty string array if not set during decode
filters, // defaults to undefined if not set during decode
Expand All @@ -87,16 +98,21 @@ export const addPrepackagedRulesSchema = t.intersection([
interval: DefaultIntervalString, // defaults to "5m" if not set during decode
query, // defaults to undefined if not set during decode
language, // defaults to undefined if not set during decode
license, // defaults to "undefined" if not set during decode
saved_id, // defaults to "undefined" if not set during decode
timeline_id, // defaults to "undefined" if not set during decode
timeline_title, // defaults to "undefined" if not set during decode
meta, // defaults to "undefined" if not set during decode
machine_learning_job_id, // defaults to "undefined" if not set during decode
max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode
risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode
rule_name_override, // defaults to "undefined" if not set during decode
severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode
tags: DefaultStringArray, // defaults to empty string array if not set during decode
to: DefaultToString, // defaults to "now" if not set during decode
threat: DefaultThreatArray, // defaults to empty array if not set during decode
throttle: DefaultThrottleNull, // defaults to "null" if not set during decode
timestamp_override, // defaults to "undefined" if not set during decode
references: DefaultStringArray, // defaults to empty array of strings if not set during decode
note, // defaults to "undefined" if not set during decode
exceptions_list: DefaultListArray, // defaults to empty array if not set during decode
Expand All @@ -109,26 +125,32 @@ export type AddPrepackagedRulesSchema = t.TypeOf<typeof addPrepackagedRulesSchem
// This type is used after a decode since some things are defaults after a decode.
export type AddPrepackagedRulesSchemaDecoded = Omit<
AddPrepackagedRulesSchema,
| 'author'
| 'references'
| 'actions'
| 'enabled'
| 'false_positives'
| 'from'
| 'interval'
| 'max_signals'
| 'risk_score_mapping'
| 'severity_mapping'
| 'tags'
| 'to'
| 'threat'
| 'throttle'
| 'exceptions_list'
> & {
author: Author;
references: References;
actions: Actions;
enabled: Enabled;
false_positives: FalsePositives;
from: From;
interval: Interval;
max_signals: MaxSignals;
risk_score_mapping: RiskScoreMapping;
severity_mapping: SeverityMapping;
tags: Tags;
to: To;
threat: Threat;
Expand Down
Expand Up @@ -261,6 +261,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
Expand Down Expand Up @@ -333,6 +336,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
Expand Down Expand Up @@ -430,6 +436,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
Expand Down Expand Up @@ -508,6 +517,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
risk_score: 50,
description: 'some description',
Expand Down Expand Up @@ -1354,6 +1366,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
Expand Down Expand Up @@ -1404,6 +1419,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
Expand Down Expand Up @@ -1462,6 +1480,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
Expand Down Expand Up @@ -1539,6 +1560,9 @@ describe('add prepackaged rules schema', () => {
const message = pipe(checked, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([]);
const expected: AddPrepackagedRulesSchemaDecoded = {
author: [],
severity_mapping: [],
risk_score_mapping: [],
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add a test where you exercise each of these array values to convince yourself they will work as well as one where you have an invalid data type such as a string instead of an array or a number instead of a string.

Then basically cut across the other schemas and add the "proof" that it validates to each of those.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I've noted and will include those in Part II!

Expand Down
Expand Up @@ -30,6 +30,9 @@ export const getCreateMlRulesSchemaMock = (ruleId = 'rule-1') => {
};

export const getCreateRulesSchemaDecodedMock = (): CreateRulesSchemaDecoded => ({
author: [],
severity_mapping: [],
risk_score_mapping: [],
description: 'Detecting root and admin users',
name: 'Query with a rule id',
query: 'user.name: root or user.name: admin',
Expand Down