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

[Detection Engine] Adds Alert Suppression to ML Rules #181926

Merged
merged 111 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
ad461bb
Add outline of integration test scenarios
rylnd Apr 26, 2024
8c0f6c1
Fleshing out more of our suppression execution tests
rylnd Apr 26, 2024
01bcf8e
Declare alert suppression fields as optional for ML rules
rylnd Apr 26, 2024
c42b339
Generated new types from new schema
rylnd Apr 26, 2024
b78c531
First legitimately failing test
rylnd Apr 26, 2024
cad4183
Extract executor params to interface
rylnd May 1, 2024
5377c6d
Merge branch 'main' into ml_rule_alert_suppression
rylnd May 3, 2024
6dbb88f
Adding more ML suppression functionality as typescript and tests dictate
rylnd May 3, 2024
e29c3d7
Declare ML rule to be suppressible
rylnd May 3, 2024
12ad5f5
Add ML rule to general suppression schema tests
rylnd May 3, 2024
c8b7c6a
Add placeholder for ML executor functionality
rylnd May 3, 2024
7f317cf
Declare our new executor parameters needed for rule suppression
rylnd May 3, 2024
13408a3
Call bulkCreateSuppressedAlertsInMemory to ML rules
rylnd May 3, 2024
703084f
Enable feature flag in FTR tests
rylnd May 3, 2024
b9de69e
Handle ML suppression params in rule converters
rylnd May 3, 2024
7e63b4d
First passing integration test
rylnd May 3, 2024
0caa342
Implementing most of our FTR test functionality
rylnd May 7, 2024
99aaffe
Flesh out remaining API integration tests
rylnd May 8, 2024
ee86fb1
Update test description in response to feedback
rylnd May 8, 2024
b5e809d
Add non-destructive form filling task for ML rules
rylnd May 8, 2024
10a9a42
Remove unused helper
rylnd May 8, 2024
f010c1c
Add cypress tests around creating/editing ML rules with suppression
rylnd May 8, 2024
5358bc4
Add TODO for later
rylnd May 8, 2024
4804eef
Add missing frontend logic to enable ML alert suppression
rylnd May 8, 2024
03833d1
Fix ML executor unit tests
rylnd May 8, 2024
184375d
Fix type error
rylnd May 9, 2024
982649f
Add alert suppression fields to alerting integration snapshot
rylnd May 9, 2024
241deaf
Allow cypress task to fill combobox with varying values
rylnd May 9, 2024
651806f
Get most of the rule editing UI tests green
rylnd May 9, 2024
c738d51
Merge branch 'main' into ml_rule_alert_suppression
rylnd May 9, 2024
c9a371e
Fix type errors in prebuilt rule cypress test
rylnd May 10, 2024
a39c6ba
Merge branch 'main' into ml_rule_alert_suppression
rylnd May 10, 2024
fe2635f
Merge branch 'main' into ml_rule_alert_suppression
rylnd May 16, 2024
fc40364
Fix nondeterministic ordering of alerts
rylnd May 16, 2024
afa7db6
Merge branch 'main' into ml_rule_alert_suppression
rylnd May 30, 2024
4a172ee
Fix missed merge conflicts
rylnd May 31, 2024
1a92d58
Remove unused dependency from test helper
rylnd May 31, 2024
60149d1
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 4, 2024
eacb0b0
Remove unnecessary linting exception
rylnd Jun 4, 2024
845a217
Fix type error in helper fn
rylnd Jun 4, 2024
3c68939
Use empty array as input index for wrapSuppressedHits
rylnd Jun 6, 2024
3914d74
Test that some timing information is used in the ML executor
rylnd Jun 6, 2024
3f5d9c2
Remove TODO that we won't touch
rylnd Jun 6, 2024
3d8a11e
Remove placeholder comment
rylnd Jun 6, 2024
7901b5c
Remove unused variable
rylnd Jun 6, 2024
3b25273
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 7, 2024
f8145a2
Update test following upstream behavioral change
rylnd Jun 7, 2024
7aa764a
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 10, 2024
c54e3e9
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 10, 2024
9c79daf
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 10, 2024
922d8b0
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 11, 2024
56de5f6
Add copy for new ML rule warnings
rylnd Jun 6, 2024
661a195
Add UX messaging for ML suppression edge cases
rylnd Jun 6, 2024
dfeb857
style: sort StepDefineRule arguments
rylnd Jun 6, 2024
5273d1f
Provide ML fields on Define step of rule creation
rylnd Jun 6, 2024
4955831
WIP: adding cypress tests for ML warning
rylnd Jun 11, 2024
eefa1c0
Install test ML jobs with a known/compatible group
rylnd Jun 12, 2024
cf5ceae
Use correct job ID in cypress tests
rylnd Jun 12, 2024
87c079d
Replace duplicated, magic-stringed function for existing one
rylnd Jun 12, 2024
cf71e6d
ML Suppression Cypress tests are green
rylnd Jun 12, 2024
65e16e8
More direct copy for user action
rylnd Jun 17, 2024
e6d85e2
Update ML warning copy per docs' suggestions
rylnd Jun 18, 2024
9c2d143
Replace display names with actual Job IDs
rylnd Jun 17, 2024
1aa170a
Extract ML rule validation logic into hook
rylnd Jun 17, 2024
fedb755
Relax requirement on number of rules
rylnd Jun 18, 2024
61d24d4
Clean up existing ML cypress test
rylnd Jun 18, 2024
99deac3
Bring back job display names for assertions
rylnd Jun 18, 2024
b0970bf
Re-enable ML rule edit tests
rylnd Jun 18, 2024
1a99fd1
Robustify ML cypress tasks
rylnd Jun 18, 2024
742503b
Ensure test has clean setup
rylnd Jun 18, 2024
f5cbaa5
Remove exclusivity from FTR tests, add TODO for re-skipping
rylnd Jun 18, 2024
8e9d4c5
Fix suppression fields for non-ML cases
rylnd Jun 18, 2024
e6aae21
Merge pull request #9 from rylnd/ml_rule_suppression_warnings
rylnd Jun 18, 2024
0cf3a51
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 18, 2024
792373e
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jun 18, 2024
a3117a1
Revert change to form field types
rylnd Jun 18, 2024
9e77939
Update cypress tests to reflect latest copy
rylnd Jun 18, 2024
a42b966
Better assertion
rylnd Jun 18, 2024
df8e8a2
Fix mappings/aliases for v3 ML job archive
rylnd Jun 18, 2024
d71d5b0
Fix test assertion by loosening constraints
rylnd Jun 18, 2024
5ab6506
Stop ALL potential jobs at the start of our ML tests
rylnd Jun 18, 2024
5f1e4fa
Fix type error in test helper
rylnd Jun 19, 2024
87c3331
Reset combobox state after every option "fill"
rylnd Jun 20, 2024
a789766
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 21, 2024
8f18a8e
Stop both datafeeds and jobs between ML tests
rylnd Jun 21, 2024
f451762
Don't fail if no jobs found during setup
rylnd Jun 21, 2024
32a55fb
Disable ML suppression fields until they're loaded and available
rylnd Jun 21, 2024
072e2f7
Add ML feature flag for serverless cypress
rylnd Jun 21, 2024
e78404c
Add read access to ML indices for security roles
rylnd Jun 24, 2024
240e998
Skip FF-dependent cypress tests in MKI builds
rylnd Jun 24, 2024
61ea144
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 26, 2024
550bc8e
Fix type error in integration test
rylnd Jun 26, 2024
798fcb0
Add missing ML privileges from other role definitions
rylnd Jun 27, 2024
8462043
Re-skip flaky ML API tests
rylnd Jun 27, 2024
33ad209
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 27, 2024
3168eac
Extract hairy inline logic to named variables
rylnd Jun 27, 2024
307e964
Update copy to be more direct
rylnd Jun 27, 2024
6ce66d1
More precise instruction
rylnd Jun 28, 2024
98ac972
Inline function-specific parameter
rylnd Jun 28, 2024
d152289
Add ML rules to Basic/Essentials workflow tests
rylnd Jun 28, 2024
ce395da
Add additional non-suppressible alert to better demonstrate suppression
rylnd Jun 28, 2024
f56840d
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jun 28, 2024
d666eb0
Remove unused helper
rylnd Jun 28, 2024
112dafe
Add comment indicating why tests are skipped in MKI
rylnd Jul 1, 2024
006c88c
DRY up helper predicates
rylnd Jul 1, 2024
dd897d6
Send suppressed ML alerts to timeline as normal alerts
rylnd Jul 1, 2024
1924596
Merge branch 'main' into ml_rule_alert_suppression
rylnd Jul 1, 2024
96141dd
Test prebuilt rule workflows with ML Alert Suppression
rylnd Jul 1, 2024
3aa105d
Abstract ML-related logic into helper hook
rylnd Jul 1, 2024
a9b410d
Remove unused parameter
rylnd Jul 1, 2024
8e07763
Skip FTR suppression tests in MKI
rylnd Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,7 @@ describe('rules schema', () => {
{ ruleType: 'saved_query', ruleMock: getCreateSavedQueryRulesSchemaMock() },
{ ruleType: 'eql', ruleMock: getCreateEqlRuleSchemaMock() },
{ ruleType: 'new_terms', ruleMock: getCreateNewTermsRulesSchemaMock() },
{ ruleType: 'machine_learning', ruleMock: getCreateMachineLearningRulesSchemaMock() },
];

cases.forEach(({ ruleType, ruleMock }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,14 +468,25 @@ export const MachineLearningRuleRequiredFields = z.object({
machine_learning_job_id: MachineLearningJobId,
});

export type MachineLearningRuleOptionalFields = z.infer<typeof MachineLearningRuleOptionalFields>;
export const MachineLearningRuleOptionalFields = z.object({
alert_suppression: AlertSuppression.optional(),
});

export type MachineLearningRulePatchFields = z.infer<typeof MachineLearningRulePatchFields>;
export const MachineLearningRulePatchFields = MachineLearningRuleRequiredFields.partial();
export const MachineLearningRulePatchFields = MachineLearningRuleRequiredFields.partial().merge(
MachineLearningRuleOptionalFields
);

export type MachineLearningRuleResponseFields = z.infer<typeof MachineLearningRuleResponseFields>;
export const MachineLearningRuleResponseFields = MachineLearningRuleRequiredFields;
export const MachineLearningRuleResponseFields = MachineLearningRuleRequiredFields.merge(
MachineLearningRuleOptionalFields
);

export type MachineLearningRuleCreateFields = z.infer<typeof MachineLearningRuleCreateFields>;
export const MachineLearningRuleCreateFields = MachineLearningRuleRequiredFields;
export const MachineLearningRuleCreateFields = MachineLearningRuleRequiredFields.merge(
MachineLearningRuleOptionalFields
);

export type MachineLearningRule = z.infer<typeof MachineLearningRule>;
export const MachineLearningRule = SharedResponseProps.merge(MachineLearningRuleResponseFields);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -686,18 +686,27 @@ components:
- machine_learning_job_id
- anomaly_threshold

MachineLearningRuleOptionalFields:
type: object
properties:
alert_suppression:
$ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression'

MachineLearningRulePatchFields:
allOf:
- $ref: '#/components/schemas/MachineLearningRuleRequiredFields'
x-modify: partial
- $ref: '#/components/schemas/MachineLearningRuleOptionalFields'

MachineLearningRuleResponseFields:
allOf:
- $ref: '#/components/schemas/MachineLearningRuleRequiredFields'
- $ref: '#/components/schemas/MachineLearningRuleOptionalFields'

MachineLearningRuleCreateFields:
allOf:
- $ref: '#/components/schemas/MachineLearningRuleRequiredFields'
- $ref: '#/components/schemas/MachineLearningRuleOptionalFields'

MachineLearningRule:
allOf:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const SUPPRESSIBLE_ALERT_RULES: Type[] = [
'new_terms',
'threat_match',
'eql',
'machine_learning',
];

export const SUPPRESSIBLE_ALERT_RULES_GA: Type[] = ['saved_query', 'query'];
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,7 @@ describe('Alert Suppression Rules', () => {
expect(isSuppressibleAlertRule('threat_match')).toBe(true);
expect(isSuppressibleAlertRule('new_terms')).toBe(true);
expect(isSuppressibleAlertRule('eql')).toBe(true);

// Rule types that don't support alert suppression:
expect(isSuppressibleAlertRule('machine_learning')).toBe(false);
expect(isSuppressibleAlertRule('machine_learning')).toBe(true);
});

test('should return false for an unknown rule type', () => {
Expand Down Expand Up @@ -273,9 +271,7 @@ describe('Alert Suppression Rules', () => {
expect(isSuppressionRuleConfiguredWithDuration('threat_match')).toBe(true);
expect(isSuppressionRuleConfiguredWithDuration('new_terms')).toBe(true);
expect(isSuppressionRuleConfiguredWithDuration('eql')).toBe(true);

// Rule types that don't support alert suppression:
expect(isSuppressionRuleConfiguredWithDuration('machine_learning')).toBe(false);
expect(isSuppressionRuleConfiguredWithDuration('machine_learning')).toBe(true);
});

test('should return false for an unknown rule type', () => {
Expand All @@ -294,9 +290,7 @@ describe('Alert Suppression Rules', () => {
expect(isSuppressionRuleConfiguredWithGroupBy('threat_match')).toBe(true);
expect(isSuppressionRuleConfiguredWithGroupBy('new_terms')).toBe(true);
expect(isSuppressionRuleConfiguredWithGroupBy('eql')).toBe(true);

// Rule types that don't support alert suppression:
expect(isSuppressionRuleConfiguredWithGroupBy('machine_learning')).toBe(false);
expect(isSuppressionRuleConfiguredWithGroupBy('machine_learning')).toBe(true);
});

test('should return false for a threshold rule type', () => {
Expand All @@ -320,9 +314,7 @@ describe('Alert Suppression Rules', () => {
expect(isSuppressionRuleConfiguredWithMissingFields('threat_match')).toBe(true);
expect(isSuppressionRuleConfiguredWithMissingFields('new_terms')).toBe(true);
expect(isSuppressionRuleConfiguredWithMissingFields('eql')).toBe(true);

// Rule types that don't support alert suppression:
expect(isSuppressionRuleConfiguredWithMissingFields('machine_learning')).toBe(false);
expect(isSuppressionRuleConfiguredWithMissingFields('machine_learning')).toBe(true);
});

test('should return false for a threshold rule type', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ export const allowedExperimentalValues = Object.freeze({
*/
riskEnginePrivilegesRouteEnabled: true,

/**
* Enables alerts suppression for machine learning rules
*/
alertSuppressionForMachineLearningRuleEnabled: false,

/**
* Enables experimental Experimental S1 integration data to be available in Analyzer
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
buildListItems,
getDescriptionItem,
} from '.';
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';

import { FilterManager, UI_SETTINGS } from '@kbn/data-plugin/public';
import type { Filter } from '@kbn/es-query';
Expand Down Expand Up @@ -575,7 +574,6 @@ describe('description_step', () => {
});

describe('alert suppression', () => {
const ruleTypesWithoutSuppression: Type[] = ['machine_learning'];
const suppressionFields = {
groupByDuration: {
unit: 'm',
Expand All @@ -587,23 +585,6 @@ describe('description_step', () => {
suppressionMissingFields: 'suppress',
};
describe('groupByDuration', () => {
ruleTypesWithoutSuppression.forEach((ruleType) => {
test(`should be empty if rule is ${ruleType}`, () => {
const result: ListItems[] = getDescriptionItem(
'groupByDuration',
'label',
{
ruleType,
...suppressionFields,
},
mockFilterManager,
mockLicenseService
);

expect(result).toEqual([]);
});
});

['query', 'saved_query'].forEach((ruleType) => {
test(`should be empty if groupByFields empty for ${ruleType} rule`, () => {
const result: ListItems[] = getDescriptionItem(
Expand Down Expand Up @@ -686,22 +667,21 @@ describe('description_step', () => {
});

describe('groupByFields', () => {
[...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => {
test(`should be empty if rule is ${ruleType}`, () => {
const result: ListItems[] = getDescriptionItem(
'groupByFields',
'label',
{
ruleType,
...suppressionFields,
},
mockFilterManager,
mockLicenseService
);
test(`should be empty if rule type is 'threshold'`, () => {
const result: ListItems[] = getDescriptionItem(
'groupByFields',
'label',
{
ruleType: 'threshold',
...suppressionFields,
},
mockFilterManager,
mockLicenseService
);

expect(result).toEqual([]);
});
expect(result).toEqual([]);
});

['query', 'saved_query'].forEach((ruleType) => {
test(`should return item for ${ruleType} rule`, () => {
const result: ListItems[] = getDescriptionItem(
Expand All @@ -720,22 +700,21 @@ describe('description_step', () => {
});

describe('suppressionMissingFields', () => {
[...ruleTypesWithoutSuppression, 'threshold'].forEach((ruleType) => {
test(`should be empty if rule is ${ruleType}`, () => {
const result: ListItems[] = getDescriptionItem(
'suppressionMissingFields',
'label',
{
ruleType,
...suppressionFields,
},
mockFilterManager,
mockLicenseService
);
test(`should be empty if rule type is 'threshold'`, () => {
const result: ListItems[] = getDescriptionItem(
'suppressionMissingFields',
'label',
{
ruleType: 'threshold',
...suppressionFields,
},
mockFilterManager,
mockLicenseService
);

expect(result).toEqual([]);
});
expect(result).toEqual([]);
});

['query', 'saved_query'].forEach((ruleType) => {
test(`should return item for ${ruleType} rule`, () => {
const result: ListItems[] = getDescriptionItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,26 @@
import { useCallback } from 'react';
import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { isEsqlRule } from '../../../../../common/detection_engine/utils';
import { isEsqlRule, isMlRule } from '../../../../../common/detection_engine/utils';

/**
* transforms DefineStepRule fields according to experimental feature flags
*/
export const useExperimentalFeatureFieldsTransform = <T extends Partial<DefineStepRule>>(): ((
fields: T
) => T) => {
const isAlertSuppressionForMachineLearningRuleEnabled = useIsExperimentalFeatureEnabled(
'alertSuppressionForMachineLearningRuleEnabled'
);
const isAlertSuppressionForEsqlRuleEnabled = useIsExperimentalFeatureEnabled(
'alertSuppressionForEsqlRuleEnabled'
);

const transformer = useCallback(
(fields: T) => {
const isSuppressionDisabled =
isEsqlRule(fields.ruleType) && !isAlertSuppressionForEsqlRuleEnabled;
(isMlRule(fields.ruleType) && !isAlertSuppressionForMachineLearningRuleEnabled) ||
(isEsqlRule(fields.ruleType) && !isAlertSuppressionForEsqlRuleEnabled);

// reset any alert suppression values hidden behind feature flag
if (isSuppressionDisabled) {
Expand All @@ -38,7 +42,7 @@ export const useExperimentalFeatureFieldsTransform = <T extends Partial<DefineSt

return fields;
},
[isAlertSuppressionForEsqlRuleEnabled]
[isAlertSuppressionForEsqlRuleEnabled, isAlertSuppressionForMachineLearningRuleEnabled]
);

return transformer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,32 @@ describe('helpers', () => {

expect(result).toEqual(expected);
});

it('returns suppression fields for machine_learning rules', () => {
const mockStepData: DefineStepRule = {
...mockData,
ruleType: 'machine_learning',
machineLearningJobId: ['some_jobert_id'],
anomalyThreshold: 44,
groupByFields: ['event.type'],
groupByRadioSelection: GroupByOptions.PerTimePeriod,
groupByDuration: { value: 10, unit: 'm' },
};
const result = formatDefineStepData(mockStepData);

const expected: DefineStepRuleJson = {
machine_learning_job_id: ['some_jobert_id'],
anomaly_threshold: 44,
type: 'machine_learning',
alert_suppression: {
group_by: ['event.type'],
duration: { value: 10, unit: 'm' },
missing_fields_strategy: 'suppress',
},
};

expect(result).toEqual(expect.objectContaining(expected));
});
});

describe('formatScheduleStepData', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep
? {
anomaly_threshold: ruleFields.anomalyThreshold,
machine_learning_job_id: ruleFields.machineLearningJobId,
...alertSuppressionFields,
}
: isThresholdFields(ruleFields)
? {
Expand Down
Loading