mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ece Özalp <ozale272@newschool.edu>
This commit is contained in:
parent
4ea934c2ce
commit
4ec95de6f6
9 changed files with 231 additions and 7 deletions
|
@ -40,6 +40,7 @@ import {
|
|||
INDICATOR_INDEX_PATTERNS,
|
||||
INDICATOR_INDEX_QUERY,
|
||||
INDICATOR_MAPPING,
|
||||
INDICATOR_PREFIX_OVERRIDE,
|
||||
INVESTIGATION_NOTES_MARKDOWN,
|
||||
INVESTIGATION_NOTES_TOGGLE,
|
||||
MITRE_ATTACK_DETAILS,
|
||||
|
@ -448,6 +449,10 @@ describe('indicator match', () => {
|
|||
cy.get(ABOUT_DETAILS).within(() => {
|
||||
getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
|
||||
getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
|
||||
getDetails(INDICATOR_PREFIX_OVERRIDE).should(
|
||||
'have.text',
|
||||
getNewThreatIndicatorRule().threatIndicatorPath
|
||||
);
|
||||
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
|
||||
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
|
||||
});
|
||||
|
|
|
@ -77,6 +77,7 @@ export interface ThreatIndicatorRule extends CustomRule {
|
|||
indicatorIndexPattern: string[];
|
||||
indicatorMappingField: string;
|
||||
indicatorIndexField: string;
|
||||
threatIndicatorPath: string;
|
||||
type?: string;
|
||||
atomic?: string;
|
||||
}
|
||||
|
@ -405,6 +406,7 @@ export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({
|
|||
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
|
||||
timeline: getIndicatorMatchTimelineTemplate(),
|
||||
maxSignals: 100,
|
||||
threatIndicatorPath: 'threat.indicator',
|
||||
});
|
||||
|
||||
export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`;
|
||||
|
|
|
@ -64,6 +64,8 @@ export const RULE_NAME_OVERRIDE_DETAILS = 'Rule name override';
|
|||
|
||||
export const RISK_SCORE_DETAILS = 'Risk score';
|
||||
|
||||
export const INDICATOR_PREFIX_OVERRIDE = 'Indicator prefix override';
|
||||
|
||||
export const RISK_SCORE_OVERRIDE_DETAILS = 'Risk score override';
|
||||
|
||||
export const REFERENCE_URLS_DETAILS = 'Reference URLs';
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
AboutStepRule,
|
||||
RuleStepsFormHooks,
|
||||
RuleStep,
|
||||
DefineStepRule,
|
||||
} from '../../../pages/detection_engine/rules/types';
|
||||
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
|
||||
import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock';
|
||||
|
@ -105,6 +106,63 @@ describe.skip('StepAboutRuleComponent', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('is invalid if threat match rule and threat_indicator_path is not present', async () => {
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
defaultValues={stepAboutDefaultValue}
|
||||
defineRuleData={{ ruleType: 'threat_match' } as DefineStepRule}
|
||||
descriptionColumns="multi"
|
||||
isReadOnlyView={false}
|
||||
setForm={setFormHook}
|
||||
isLoading={false}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
if (!formHook) {
|
||||
throw new Error('Form hook not set, but tests depend on it');
|
||||
}
|
||||
wrapper
|
||||
.find('[data-test-subj="detectionEngineStepAboutThreatIndicatorPath"] input')
|
||||
.first()
|
||||
.simulate('change', { target: { value: '' } });
|
||||
|
||||
const result = await formHook();
|
||||
expect(result?.isValid).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('is valid if is not a threat match rule and threat_indicator_path is not present', async () => {
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
defaultValues={stepAboutDefaultValue}
|
||||
descriptionColumns="multi"
|
||||
isReadOnlyView={false}
|
||||
setForm={setFormHook}
|
||||
isLoading={false}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
if (!formHook) {
|
||||
throw new Error('Form hook not set, but tests depend on it');
|
||||
}
|
||||
wrapper
|
||||
.find('[data-test-subj="detectionEngineStepAboutThreatIndicatorPath"] input')
|
||||
.first()
|
||||
.simulate('change', { target: { value: '' } });
|
||||
|
||||
const result = await formHook();
|
||||
expect(result?.isValid).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('is invalid if no "name" is present', async () => {
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui';
|
||||
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FC, memo, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
|
@ -31,7 +31,7 @@ import {
|
|||
import { defaultRiskScoreBySeverity, severityOptions } from './data';
|
||||
import { stepAboutDefaultValue } from './default_value';
|
||||
import { isUrlInvalid } from '../../../../common/utils/validators';
|
||||
import { schema } from './schema';
|
||||
import { schema as defaultSchema, threatIndicatorPathRequiredSchemaValue } from './schema';
|
||||
import * as I18n from './translations';
|
||||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
import { NextStep } from '../next_step';
|
||||
|
@ -73,7 +73,28 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
|||
onSubmit,
|
||||
setForm,
|
||||
}) => {
|
||||
const initialState = defaultValues ?? stepAboutDefaultValue;
|
||||
const isThreatMatchRuleValue = useMemo(
|
||||
() => isThreatMatchRule(defineRuleData?.ruleType),
|
||||
[defineRuleData?.ruleType]
|
||||
);
|
||||
|
||||
const initialState: AboutStepRule = useMemo(
|
||||
() =>
|
||||
defaultValues ??
|
||||
(isThreatMatchRuleValue
|
||||
? { ...stepAboutDefaultValue, threatIndicatorPath: DEFAULT_INDICATOR_SOURCE_PATH }
|
||||
: stepAboutDefaultValue),
|
||||
[defaultValues, isThreatMatchRuleValue]
|
||||
);
|
||||
|
||||
const schema = useMemo(
|
||||
() =>
|
||||
isThreatMatchRuleValue
|
||||
? { ...defaultSchema, threatIndicatorPath: threatIndicatorPathRequiredSchemaValue }
|
||||
: defaultSchema,
|
||||
[isThreatMatchRuleValue]
|
||||
);
|
||||
|
||||
const [severityValue, setSeverityValue] = useState<string>(initialState.severity.value);
|
||||
const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []);
|
||||
|
||||
|
@ -300,7 +321,7 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
|||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="l" />
|
||||
{isThreatMatchRule(defineRuleData?.ruleType) && (
|
||||
{isThreatMatchRuleValue && (
|
||||
<>
|
||||
<CommonUseField
|
||||
path="threatIndicatorPath"
|
||||
|
|
|
@ -291,3 +291,33 @@ export const schema: FormSchema<AboutStepRule> = {
|
|||
labelAppend: OptionalFieldLabel,
|
||||
},
|
||||
};
|
||||
|
||||
export const threatIndicatorPathRequiredSchemaValue = {
|
||||
type: FIELD_TYPES.TEXT,
|
||||
label: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel',
|
||||
{
|
||||
defaultMessage: 'Indicator prefix override',
|
||||
}
|
||||
),
|
||||
helpText: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Specify the document prefix containing your indicator fields. Used for enrichment of indicator match alerts.',
|
||||
}
|
||||
),
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.threatIndicatorPathFieldEmptyError',
|
||||
{
|
||||
defaultMessage: 'Indicator prefix override must not be empty',
|
||||
}
|
||||
)
|
||||
),
|
||||
type: VALIDATION_TYPES.FIELD,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -117,3 +117,69 @@ export const getCreateMlRulesOptionsMock = (
|
|||
exceptionsList: [],
|
||||
actions: [],
|
||||
});
|
||||
|
||||
export const getCreateThreatMatchRulesOptionsMock = (
|
||||
isRuleRegistryEnabled: boolean
|
||||
): CreateRulesOptions => ({
|
||||
actions: [],
|
||||
anomalyThreshold: undefined,
|
||||
author: ['Elastic'],
|
||||
buildingBlockType: undefined,
|
||||
concurrentSearches: undefined,
|
||||
description: 'some description',
|
||||
enabled: true,
|
||||
eventCategoryOverride: undefined,
|
||||
exceptionsList: [],
|
||||
falsePositives: ['false positive 1', 'false positive 2'],
|
||||
filters: [],
|
||||
from: 'now-1m',
|
||||
immutable: false,
|
||||
index: ['*'],
|
||||
interval: '5m',
|
||||
isRuleRegistryEnabled,
|
||||
itemsPerSearch: undefined,
|
||||
language: 'kuery',
|
||||
license: 'Elastic License',
|
||||
machineLearningJobId: undefined,
|
||||
maxSignals: 100,
|
||||
meta: {},
|
||||
name: 'Query with a rule id',
|
||||
note: '# sample markdown',
|
||||
outputIndex: 'output-1',
|
||||
query: 'user.name: root or user.name: admin',
|
||||
references: ['http://www.example.com'],
|
||||
riskScore: 80,
|
||||
riskScoreMapping: [],
|
||||
ruleId: 'rule-1',
|
||||
ruleNameOverride: undefined,
|
||||
rulesClient: rulesClientMock.create(),
|
||||
savedId: 'savedId-123',
|
||||
severity: 'high',
|
||||
severityMapping: [],
|
||||
tags: [],
|
||||
threat: [],
|
||||
threatFilters: undefined,
|
||||
threatIndex: ['filebeat-*'],
|
||||
threatIndicatorPath: 'threat.indicator',
|
||||
threatLanguage: 'kuery',
|
||||
threatMapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'file.hash.md5',
|
||||
type: 'mapping',
|
||||
value: 'threat.indicator.file.hash.md5',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
threatQuery: '*:*',
|
||||
threshold: undefined,
|
||||
throttle: null,
|
||||
timelineId: 'timelineid-123',
|
||||
timelineTitle: 'timeline-title-123',
|
||||
timestampOverride: undefined,
|
||||
to: 'now',
|
||||
type: 'threat_match',
|
||||
version: 1,
|
||||
});
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
*/
|
||||
|
||||
import { createRules } from './create_rules';
|
||||
import { getCreateMlRulesOptionsMock } from './create_rules.mock';
|
||||
import {
|
||||
getCreateMlRulesOptionsMock,
|
||||
getCreateThreatMatchRulesOptionsMock,
|
||||
} from './create_rules.mock';
|
||||
import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../common/constants';
|
||||
|
||||
describe.each([
|
||||
['Legacy', false],
|
||||
|
@ -44,4 +48,34 @@ describe.each([
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('populates a threatIndicatorPath value for threat_match rule if empty', async () => {
|
||||
const ruleOptions = getCreateThreatMatchRulesOptionsMock(isRuleRegistryEnabled);
|
||||
delete ruleOptions.threatIndicatorPath;
|
||||
await createRules(ruleOptions);
|
||||
expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
threatIndicatorPath: DEFAULT_INDICATOR_SOURCE_PATH,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('does not populate a threatIndicatorPath value for other rules if empty', async () => {
|
||||
const ruleOptions = getCreateMlRulesOptionsMock(isRuleRegistryEnabled);
|
||||
delete ruleOptions.threatIndicatorPath;
|
||||
await createRules(ruleOptions);
|
||||
expect(ruleOptions.rulesClient.create).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
threatIndicatorPath: DEFAULT_INDICATOR_SOURCE_PATH,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,11 @@ import {
|
|||
} from '../../../../common/detection_engine/utils';
|
||||
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
||||
import { SanitizedAlert } from '../../../../../alerting/common';
|
||||
import { NOTIFICATION_THROTTLE_NO_ACTIONS, SERVER_APP_ID } from '../../../../common/constants';
|
||||
import {
|
||||
DEFAULT_INDICATOR_SOURCE_PATH,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
SERVER_APP_ID,
|
||||
} from '../../../../common/constants';
|
||||
import { CreateRulesOptions } from './types';
|
||||
import { addTags } from './add_tags';
|
||||
import { PartialFilter, RuleTypeParams } from '../types';
|
||||
|
@ -115,7 +119,9 @@ export const createRules = async ({
|
|||
*/
|
||||
threatFilters: threatFilters as PartialFilter[] | undefined,
|
||||
threatIndex,
|
||||
threatIndicatorPath,
|
||||
threatIndicatorPath:
|
||||
threatIndicatorPath ??
|
||||
(type === 'threat_match' ? DEFAULT_INDICATOR_SOURCE_PATH : undefined),
|
||||
threatQuery,
|
||||
concurrentSearches,
|
||||
itemsPerSearch,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue