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][Detections] Adds Indicator path config for indicator match rules #91260

Merged
merged 9 commits into from
Feb 17, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
threat_indicator_path,
} from '../types/threat_mapping';

import {
Expand Down Expand Up @@ -133,6 +134,7 @@ export const addPrepackagedRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
threat_indicator_path, // defaults "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
threat_indicator_path,
} from '../types/threat_mapping';

import {
Expand Down Expand Up @@ -152,6 +153,7 @@ export const importRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
threat_indicator_path, // defaults to "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
threat_indicator_path,
} from '../types/threat_mapping';
import { listArrayOrUndefined } from '../types/lists';

Expand Down Expand Up @@ -112,6 +113,7 @@ export const patchRulesSchema = t.exact(
threat_filters,
threat_mapping,
threat_language,
threat_indicator_path,
concurrent_searches,
items_per_search,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const getCreateThreatMatchRulesSchemaMock = (
rule_id: ruleId,
threat_query: '*:*',
threat_index: ['list-index'],
threat_indicator_path: 'threat.indicator',
threat_mapping: [
{
entries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ describe('create rules schema', () => {
});
});

describe('threat_mapping', () => {
describe('threat_match', () => {
test('You can set a threat query, index, mapping, filters when creating a rule', () => {
const payload = getCreateThreatMatchRulesSchemaMock();
const decoded = createRulesSchema.decode(payload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
threat_query,
threat_mapping,
threat_index,
threat_indicator_path,
concurrent_searches,
items_per_search,
} from '../types/threat_mapping';
Expand Down Expand Up @@ -213,6 +214,7 @@ const threatMatchRuleParams = {
filters,
saved_id,
threat_filters,
threat_indicator_path,
threat_language: t.keyof({ kuery: null, lucene: null }),
concurrent_searches,
items_per_search,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial<Rul
language: 'kuery',
threat_query: '*:*',
threat_index: ['list-index'],
threat_indicator_path: 'threat.indicator',
threat_mapping: [
{
entries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,9 +763,9 @@ describe('rules_schema', () => {
expect(fields).toEqual(expected);
});

test('should return 8 fields for a rule of type "threat_match"', () => {
test('should return nine (9) fields for a rule of type "threat_match"', () => {
const fields = addThreatMatchFields({ type: 'threat_match' });
expect(fields.length).toEqual(8);
expect(fields.length).toEqual(9);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
threat_indicator_path,
} from '../types/threat_mapping';

import { DefaultListArray } from '../types/lists_default_array';
Expand Down Expand Up @@ -151,6 +152,7 @@ export const dependentRulesSchema = t.partial({
items_per_search,
threat_mapping,
threat_language,
threat_indicator_path,
});

/**
Expand Down Expand Up @@ -286,6 +288,9 @@ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly):
t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })),
t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })),
t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })),
t.exact(
t.partial({ threat_indicator_path: dependentRulesSchema.props.threat_indicator_path })
),
t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })),
t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })),
t.exact(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export type ThreatQuery = t.TypeOf<typeof threat_query>;
export const threatQueryOrUndefined = t.union([threat_query, t.undefined]);
export type ThreatQueryOrUndefined = t.TypeOf<typeof threatQueryOrUndefined>;

export const threat_indicator_path = t.string;
export type ThreatIndicatorPath = t.TypeOf<typeof threat_indicator_path>;
export const threatIndicatorPathOrUndefined = t.union([threat_indicator_path, t.undefined]);
export type ThreatIndicatorPathOrUndefined = t.TypeOf<typeof threatIndicatorPathOrUndefined>;

export const threat_filters = t.array(t.unknown); // Filters are not easily type-able yet
export type ThreatFilters = t.TypeOf<typeof threat_filters>;
export const threatFiltersOrUndefined = t.union([threat_filters, t.undefined]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === '
export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold';
export const isQueryRule = (ruleType: Type | undefined): boolean =>
ruleType === 'query' || ruleType === 'saved_query';
export const isThreatMatchRule = (ruleType: Type): boolean => ruleType === 'threat_match';
export const isThreatMatchRule = (ruleType: Type | undefined): boolean =>
ruleType === 'threat_match';
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const stepAboutDefaultValue: AboutStepRule = {
license: '',
ruleNameOverride: '',
tags: [],
threatIndicatorPath: '',
timestampOverride: '',
threat: threatDefault,
note: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { SeverityField } from '../severity_mapping';
import { RiskScoreField } from '../risk_score_mapping';
import { AutocompleteField } from '../autocomplete_field';
import { useFetchIndex } from '../../../../common/containers/source';
import { isThreatMatchRule } from '../../../../../common/detection_engine/utils';

const CommonUseField = getUseField({ component: Field });

Expand Down Expand Up @@ -298,6 +299,23 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
/>
</EuiFormRow>
<EuiSpacer size="l" />
{isThreatMatchRule(defineRuleData?.ruleType) && (
<>
<CommonUseField
path="threatIndicatorPath"
componentProps={{
idAria: 'detectionEngineStepAboutThreatIndicatorPath',
'data-test-subj': 'detectionEngineStepAboutThreatIndicatorPath',
euiFieldProps: {
fullWidth: true,
disabled: isLoading,
placeholder: 'threat.indicator',
},
}}
/>
</>
)}
<EuiSpacer size="l" />
<UseField
path="ruleNameOverride"
component={AutocompleteField}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,23 @@ export const schema: FormSchema<AboutStepRule> = {
),
labelAppend: OptionalFieldLabel,
},
threatIndicatorPath: {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel',
{
defaultMessage: 'Threat Indicator Path',
}
),
helpText: i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText',
{
defaultMessage:
'Specify the document path containing your threat indicator fields. Used for enrichment of indicator match alerts. Defaults to threat.indicator unless otherwise specified.',
}
),
labelAppend: OptionalFieldLabel,
},
timestampOverride: {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
listArray,
threat_query,
threat_index,
threat_indicator_path,
threat_mapping,
threat_language,
threat_filters,
Expand Down Expand Up @@ -132,6 +133,7 @@ export const RuleSchema = t.intersection([
threat_query,
threat_filters,
threat_index,
threat_indicator_path,
threat_mapping,
threat_language,
timeline_id: t.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,15 @@ describe('helpers', () => {
expect(result.exceptions_list).toEqual([getListMock()]);
});

test('returns a threat indicator path', () => {
mockData = {
...mockData,
threatIndicatorPath: 'my_custom.path',
};
const result = formatAboutStepData(mockData);
expect(result.threat_indicator_path).toEqual('my_custom.path');
});
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 You have some testing! Thanks


test('returns formatted object with both exceptions_lists', () => {
const result = formatAboutStepData(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ export const formatAboutStepData = (
isBuildingBlock,
note,
ruleNameOverride,
threatIndicatorPath,
timestampOverride,
...rest
} = aboutStepData;
Expand Down Expand Up @@ -330,6 +331,7 @@ export const formatAboutStepData = (
...singleThreat,
framework: 'MITRE ATT&CK',
})),
threat_indicator_path: threatIndicatorPath,
timestamp_override: timestampOverride !== '' ? timestampOverride : undefined,
...(!isEmpty(note) ? { note } : {}),
...rest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ describe('rule helpers', () => {
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
tags: ['tag1', 'tag2'],
threat: getThreatMock(),
threatIndicatorPath: '',
timestampOverride: 'event.ingested',
};
const scheduleRuleStepData = { from: '0s', interval: '5m' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
risk_score: riskScore,
tags,
threat,
threat_indicator_path: threatIndicatorPath,
} = rule;

return {
Expand All @@ -179,6 +180,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
},
falsePositives,
threat: threat as Threats,
threatIndicatorPath: threatIndicatorPath ?? '',
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface AboutStepRule {
ruleNameOverride: string;
tags: string[];
timestampOverride: string;
threatIndicatorPath?: string;
threat: Threats;
note: string;
}
Expand Down Expand Up @@ -186,6 +187,7 @@ export interface AboutStepRuleJson {
rule_name_override?: RuleNameOverride;
tags: string[];
threat: Threats;
threat_indicator_path?: string;
timestamp_override?: TimestampOverride;
note?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export const getResult = (): RuleAlertType => ({
threatMapping: undefined,
threatLanguage: undefined,
threatIndex: undefined,
threatIndicatorPath: undefined,
threatQuery: undefined,
references: ['http://www.example.com', 'https://ww.example.com'],
note: '# Investigative notes',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export const importRulesRoute = (
threat_query: threatQuery,
threat_mapping: threatMapping,
threat_language: threatLanguage,
threat_indicator_path: threatIndicatorPath,
concurrent_searches: concurrentSearches,
items_per_search: itemsPerSearch,
threshold,
Expand Down Expand Up @@ -239,6 +240,7 @@ export const importRulesRoute = (
threshold,
threatFilters,
threatIndex,
threatIndicatorPath,
threatQuery,
threatMapping,
threatLanguage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const transformAlertToRule = (
threshold: alert.params.threshold,
threat_filters: alert.params.threatFilters,
threat_index: alert.params.threatIndex,
threat_indicator_path: alert.params.threatIndicatorPath,
threat_query: alert.params.threatQuery,
threat_mapping: alert.params.threatMapping,
threat_language: alert.params.threatLanguage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({
itemsPerSearch: undefined,
threatQuery: undefined,
threatIndex: undefined,
threatIndicatorPath: undefined,
threshold: undefined,
timestampOverride: undefined,
to: 'now',
Expand Down Expand Up @@ -94,6 +95,7 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({
threat: [],
threatFilters: undefined,
threatIndex: undefined,
threatIndicatorPath: undefined,
threatMapping: undefined,
threatQuery: undefined,
threatLanguage: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const createRules = async ({
threat,
threatFilters,
threatIndex,
threatIndicatorPath,
threatLanguage,
concurrentSearches,
itemsPerSearch,
Expand Down Expand Up @@ -102,6 +103,7 @@ export const createRules = async ({
*/
threatFilters: threatFilters as PartialFilter[] | undefined,
threatIndex,
threatIndicatorPath,
threatQuery,
concurrentSearches,
itemsPerSearch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const installPrepackagedRules = (
items_per_search: itemsPerSearch,
threat_query: threatQuery,
threat_index: threatIndex,
threat_indicator_path: threatIndicatorPath,
threshold,
timestamp_override: timestampOverride,
references,
Expand Down Expand Up @@ -110,6 +111,7 @@ export const installPrepackagedRules = (
itemsPerSearch,
threatQuery,
threatIndex,
threatIndicatorPath,
threshold,
timestampOverride,
references,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const rule: SanitizedAlert<RuleTypeParams> = {
threshold: undefined,
threatFilters: undefined,
threatIndex: undefined,
threatIndicatorPath: undefined,
threatQuery: undefined,
threatMapping: undefined,
threatLanguage: undefined,
Expand Down
Loading