mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -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_PATTERNS,
|
||||||
INDICATOR_INDEX_QUERY,
|
INDICATOR_INDEX_QUERY,
|
||||||
INDICATOR_MAPPING,
|
INDICATOR_MAPPING,
|
||||||
|
INDICATOR_PREFIX_OVERRIDE,
|
||||||
INVESTIGATION_NOTES_MARKDOWN,
|
INVESTIGATION_NOTES_MARKDOWN,
|
||||||
INVESTIGATION_NOTES_TOGGLE,
|
INVESTIGATION_NOTES_TOGGLE,
|
||||||
MITRE_ATTACK_DETAILS,
|
MITRE_ATTACK_DETAILS,
|
||||||
|
@ -448,6 +449,10 @@ describe('indicator match', () => {
|
||||||
cy.get(ABOUT_DETAILS).within(() => {
|
cy.get(ABOUT_DETAILS).within(() => {
|
||||||
getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
|
getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
|
||||||
getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
|
getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
|
||||||
|
getDetails(INDICATOR_PREFIX_OVERRIDE).should(
|
||||||
|
'have.text',
|
||||||
|
getNewThreatIndicatorRule().threatIndicatorPath
|
||||||
|
);
|
||||||
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
|
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
|
||||||
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
|
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,6 +77,7 @@ export interface ThreatIndicatorRule extends CustomRule {
|
||||||
indicatorIndexPattern: string[];
|
indicatorIndexPattern: string[];
|
||||||
indicatorMappingField: string;
|
indicatorMappingField: string;
|
||||||
indicatorIndexField: string;
|
indicatorIndexField: string;
|
||||||
|
threatIndicatorPath: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
atomic?: string;
|
atomic?: string;
|
||||||
}
|
}
|
||||||
|
@ -405,6 +406,7 @@ export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({
|
||||||
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
|
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
|
||||||
timeline: getIndicatorMatchTimelineTemplate(),
|
timeline: getIndicatorMatchTimelineTemplate(),
|
||||||
maxSignals: 100,
|
maxSignals: 100,
|
||||||
|
threatIndicatorPath: 'threat.indicator',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`;
|
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 RISK_SCORE_DETAILS = 'Risk score';
|
||||||
|
|
||||||
|
export const INDICATOR_PREFIX_OVERRIDE = 'Indicator prefix override';
|
||||||
|
|
||||||
export const RISK_SCORE_OVERRIDE_DETAILS = 'Risk score override';
|
export const RISK_SCORE_OVERRIDE_DETAILS = 'Risk score override';
|
||||||
|
|
||||||
export const REFERENCE_URLS_DETAILS = 'Reference URLs';
|
export const REFERENCE_URLS_DETAILS = 'Reference URLs';
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
AboutStepRule,
|
AboutStepRule,
|
||||||
RuleStepsFormHooks,
|
RuleStepsFormHooks,
|
||||||
RuleStep,
|
RuleStep,
|
||||||
|
DefineStepRule,
|
||||||
} from '../../../pages/detection_engine/rules/types';
|
} from '../../../pages/detection_engine/rules/types';
|
||||||
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
|
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
|
||||||
import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock';
|
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 () => {
|
it('is invalid if no "name" is present', async () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<ThemeProvider theme={mockTheme}>
|
<ThemeProvider theme={mockTheme}>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui';
|
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 styled from 'styled-components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -31,7 +31,7 @@ import {
|
||||||
import { defaultRiskScoreBySeverity, severityOptions } from './data';
|
import { defaultRiskScoreBySeverity, severityOptions } from './data';
|
||||||
import { stepAboutDefaultValue } from './default_value';
|
import { stepAboutDefaultValue } from './default_value';
|
||||||
import { isUrlInvalid } from '../../../../common/utils/validators';
|
import { isUrlInvalid } from '../../../../common/utils/validators';
|
||||||
import { schema } from './schema';
|
import { schema as defaultSchema, threatIndicatorPathRequiredSchemaValue } from './schema';
|
||||||
import * as I18n from './translations';
|
import * as I18n from './translations';
|
||||||
import { StepContentWrapper } from '../step_content_wrapper';
|
import { StepContentWrapper } from '../step_content_wrapper';
|
||||||
import { NextStep } from '../next_step';
|
import { NextStep } from '../next_step';
|
||||||
|
@ -73,7 +73,28 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
setForm,
|
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 [severityValue, setSeverityValue] = useState<string>(initialState.severity.value);
|
||||||
const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []);
|
const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []);
|
||||||
|
|
||||||
|
@ -300,7 +321,7 @@ const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
<EuiSpacer size="l" />
|
<EuiSpacer size="l" />
|
||||||
{isThreatMatchRule(defineRuleData?.ruleType) && (
|
{isThreatMatchRuleValue && (
|
||||||
<>
|
<>
|
||||||
<CommonUseField
|
<CommonUseField
|
||||||
path="threatIndicatorPath"
|
path="threatIndicatorPath"
|
||||||
|
|
|
@ -291,3 +291,33 @@ export const schema: FormSchema<AboutStepRule> = {
|
||||||
labelAppend: OptionalFieldLabel,
|
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: [],
|
exceptionsList: [],
|
||||||
actions: [],
|
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 { 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([
|
describe.each([
|
||||||
['Legacy', false],
|
['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';
|
} from '../../../../common/detection_engine/utils';
|
||||||
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
||||||
import { SanitizedAlert } from '../../../../../alerting/common';
|
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 { CreateRulesOptions } from './types';
|
||||||
import { addTags } from './add_tags';
|
import { addTags } from './add_tags';
|
||||||
import { PartialFilter, RuleTypeParams } from '../types';
|
import { PartialFilter, RuleTypeParams } from '../types';
|
||||||
|
@ -115,7 +119,9 @@ export const createRules = async ({
|
||||||
*/
|
*/
|
||||||
threatFilters: threatFilters as PartialFilter[] | undefined,
|
threatFilters: threatFilters as PartialFilter[] | undefined,
|
||||||
threatIndex,
|
threatIndex,
|
||||||
threatIndicatorPath,
|
threatIndicatorPath:
|
||||||
|
threatIndicatorPath ??
|
||||||
|
(type === 'threat_match' ? DEFAULT_INDICATOR_SOURCE_PATH : undefined),
|
||||||
threatQuery,
|
threatQuery,
|
||||||
concurrentSearches,
|
concurrentSearches,
|
||||||
itemsPerSearch,
|
itemsPerSearch,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue