Skip to content

Commit

Permalink
[Security][Detections] Create Threshold-based Rule type (#71371)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski authored Jul 14, 2020
1 parent b26e319 commit 52bbfff
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 52bbfff

Please sign in to comment.