Skip to content

Commit

Permalink
[Security][Detections] Create Threshold-based Rule type (elastic#71371)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski committed Jul 14, 2020
1 parent 43252cb commit fa1ea40
Show file tree
Hide file tree
Showing 84 changed files with 1,656 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,12 @@ export type To = t.TypeOf<typeof to>;
export const toOrUndefined = t.union([to, t.undefined]);
export type ToOrUndefined = t.TypeOf<typeof toOrUndefined>;

export const type = t.keyof({ machine_learning: null, query: null, saved_query: null });
export const type = t.keyof({
machine_learning: null,
query: null,
saved_query: null,
threshold: null,
});
export type Type = t.TypeOf<typeof type>;

export const typeOrUndefined = t.union([type, t.undefined]);
Expand Down Expand Up @@ -369,6 +374,17 @@ export type Threat = t.TypeOf<typeof threat>;
export const threatOrUndefined = t.union([threat, t.undefined]);
export type ThreatOrUndefined = t.TypeOf<typeof threatOrUndefined>;

export const threshold = t.exact(
t.type({
field: t.string,
value: PositiveIntegerGreaterThanZero,
})
);
export type Threshold = t.TypeOf<typeof threshold>;

export const thresholdOrUndefined = t.union([threshold, t.undefined]);
export type ThresholdOrUndefined = t.TypeOf<typeof thresholdOrUndefined>;

export const created_at = IsoDateString;
export const updated_at = IsoDateString;
export const updated_by = t.string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
To,
type,
Threat,
threshold,
ThrottleOrNull,
note,
References,
Expand Down Expand Up @@ -111,6 +112,7 @@ export const addPrepackagedRulesSchema = t.intersection([
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
threshold, // defaults to "undefined" 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema';
import { addPrepackagedRuleValidateTypeDependents } from './add_prepackaged_rules_type_dependents';
import { getAddPrepackagedRulesSchemaMock } from './add_prepackaged_rules_schema.mock';

describe('create_rules_type_dependents', () => {
describe('add_prepackaged_rules_type_dependents', () => {
test('saved_id is required when type is saved_query and will not validate without out', () => {
const schema: AddPrepackagedRulesSchema = {
...getAddPrepackagedRulesSchemaMock(),
Expand Down Expand Up @@ -68,4 +68,26 @@ describe('create_rules_type_dependents', () => {
const errors = addPrepackagedRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']);
});

test('threshold is required when type is threshold and validates with it', () => {
const schema: AddPrepackagedRulesSchema = {
...getAddPrepackagedRulesSchemaMock(),
type: 'threshold',
};
const errors = addPrepackagedRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']);
});

test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => {
const schema: AddPrepackagedRulesSchema = {
...getAddPrepackagedRulesSchemaMock(),
type: 'threshold',
threshold: {
field: '',
value: -1,
},
};
const errors = addPrepackagedRuleValidateTypeDependents(schema);
expect(errors).toEqual(['"threshold.value" has to be bigger than 0']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[]
return [];
};

export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => {
if (rule.type === 'threshold') {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
return ['"threshold.value" has to be bigger than 0'];
} else {
return [];
}
}
return [];
};

export const addPrepackagedRuleValidateTypeDependents = (
schema: AddPrepackagedRulesSchema
): string[] => {
Expand All @@ -103,5 +116,6 @@ export const addPrepackagedRuleValidateTypeDependents = (
...validateMachineLearningJobId(schema),
...validateTimelineId(schema),
...validateTimelineTitle(schema),
...validateThreshold(schema),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
To,
type,
Threat,
threshold,
ThrottleOrNull,
note,
Version,
Expand Down Expand Up @@ -106,6 +107,7 @@ export const createRulesSchema = t.intersection([
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
threshold, // defaults to "undefined" 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,26 @@ describe('create_rules_type_dependents', () => {
const errors = createRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']);
});

test('threshold is required when type is threshold and validates with it', () => {
const schema: CreateRulesSchema = {
...getCreateRulesSchemaMock(),
type: 'threshold',
};
const errors = createRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']);
});

test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => {
const schema: CreateRulesSchema = {
...getCreateRulesSchemaMock(),
type: 'threshold',
threshold: {
field: '',
value: -1,
},
};
const errors = createRuleValidateTypeDependents(schema);
expect(errors).toEqual(['"threshold.value" has to be bigger than 0']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ export const validateTimelineTitle = (rule: CreateRulesSchema): string[] => {
return [];
};

export const validateThreshold = (rule: CreateRulesSchema): string[] => {
if (rule.type === 'threshold') {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
return ['"threshold.value" has to be bigger than 0'];
} else {
return [];
}
}
return [];
};

export const createRuleValidateTypeDependents = (schema: CreateRulesSchema): string[] => {
return [
...validateAnomalyThreshold(schema),
Expand All @@ -101,5 +114,6 @@ export const createRuleValidateTypeDependents = (schema: CreateRulesSchema): str
...validateMachineLearningJobId(schema),
...validateTimelineId(schema),
...validateTimelineTitle(schema),
...validateThreshold(schema),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
To,
type,
Threat,
threshold,
ThrottleOrNull,
note,
Version,
Expand Down Expand Up @@ -125,6 +126,7 @@ export const importRulesSchema = t.intersection([
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
threshold, // defaults to "undefined" 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,26 @@ describe('import_rules_type_dependents', () => {
const errors = importRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']);
});

test('threshold is required when type is threshold and validates with it', () => {
const schema: ImportRulesSchema = {
...getImportRulesSchemaMock(),
type: 'threshold',
};
const errors = importRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']);
});

test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => {
const schema: ImportRulesSchema = {
...getImportRulesSchemaMock(),
type: 'threshold',
threshold: {
field: '',
value: -1,
},
};
const errors = importRuleValidateTypeDependents(schema);
expect(errors).toEqual(['"threshold.value" has to be bigger than 0']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => {
return [];
};

export const validateThreshold = (rule: ImportRulesSchema): string[] => {
if (rule.type === 'threshold') {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
return ['"threshold.value" has to be bigger than 0'];
} else {
return [];
}
}
return [];
};

export const importRuleValidateTypeDependents = (schema: ImportRulesSchema): string[] => {
return [
...validateAnomalyThreshold(schema),
Expand All @@ -101,5 +114,6 @@ export const importRuleValidateTypeDependents = (schema: ImportRulesSchema): str
...validateMachineLearningJobId(schema),
...validateTimelineId(schema),
...validateTimelineTitle(schema),
...validateThreshold(schema),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
enabled,
tags,
threat,
threshold,
throttle,
references,
to,
Expand Down Expand Up @@ -89,6 +90,7 @@ export const patchRulesSchema = t.exact(
tags,
to,
threat,
threshold,
throttle,
timestamp_override,
references,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,26 @@ describe('patch_rules_type_dependents', () => {
const errors = patchRuleValidateTypeDependents(schema);
expect(errors).toEqual(['either "id" or "rule_id" must be set']);
});

test('threshold is required when type is threshold and validates with it', () => {
const schema: PatchRulesSchema = {
...getPatchRulesSchemaMock(),
type: 'threshold',
};
const errors = patchRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']);
});

test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => {
const schema: PatchRulesSchema = {
...getPatchRulesSchemaMock(),
type: 'threshold',
threshold: {
field: '',
value: -1,
},
};
const errors = patchRuleValidateTypeDependents(schema);
expect(errors).toEqual(['"threshold.value" has to be bigger than 0']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,26 @@ export const validateId = (rule: PatchRulesSchema): string[] => {
}
};

export const validateThreshold = (rule: PatchRulesSchema): string[] => {
if (rule.type === 'threshold') {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
return ['"threshold.value" has to be bigger than 0'];
} else {
return [];
}
}
return [];
};

export const patchRuleValidateTypeDependents = (schema: PatchRulesSchema): string[] => {
return [
...validateId(schema),
...validateQuery(schema),
...validateLanguage(schema),
...validateTimelineId(schema),
...validateTimelineTitle(schema),
...validateThreshold(schema),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
To,
type,
Threat,
threshold,
ThrottleOrNull,
note,
version,
Expand Down Expand Up @@ -114,6 +115,7 @@ export const updateRulesSchema = t.intersection([
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
threshold, // defaults to "undefined" 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,26 @@ describe('update_rules_type_dependents', () => {
const errors = updateRuleValidateTypeDependents(schema);
expect(errors).toEqual(['either "id" or "rule_id" must be set']);
});

test('threshold is required when type is threshold and validates with it', () => {
const schema: UpdateRulesSchema = {
...getUpdateRulesSchemaMock(),
type: 'threshold',
};
const errors = updateRuleValidateTypeDependents(schema);
expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']);
});

test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => {
const schema: UpdateRulesSchema = {
...getUpdateRulesSchemaMock(),
type: 'threshold',
threshold: {
field: '',
value: -1,
},
};
const errors = updateRuleValidateTypeDependents(schema);
expect(errors).toEqual(['"threshold.value" has to be bigger than 0']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ export const validateId = (rule: UpdateRulesSchema): string[] => {
}
};

export const validateThreshold = (rule: UpdateRulesSchema): string[] => {
if (rule.type === 'threshold') {
if (!rule.threshold) {
return ['when "type" is "threshold", "threshold" is required'];
} else if (rule.threshold.value <= 0) {
return ['"threshold.value" has to be bigger than 0'];
} else {
return [];
}
}
return [];
};

export const updateRuleValidateTypeDependents = (schema: UpdateRulesSchema): string[] => {
return [
...validateId(schema),
Expand All @@ -112,5 +125,6 @@ export const updateRuleValidateTypeDependents = (schema: UpdateRulesSchema): str
...validateMachineLearningJobId(schema),
...validateTimelineId(schema),
...validateTimelineTitle(schema),
...validateThreshold(schema),
];
};
Loading

0 comments on commit fa1ea40

Please sign in to comment.