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] Handle dupes when processing threshold rules #83062

Merged
merged 41 commits into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7ac4f7d
Fix threshold rule synthetic signal generation
madirey Nov 3, 2020
fba2636
Merge branch 'master' of github.com:elastic/kibana into cidr-house-rules
madirey Nov 3, 2020
352a139
Merge branch 'master' of github.com:elastic/kibana into cidr-house-rules
madirey Nov 5, 2020
0c9d5e3
Use top_hits aggregation
madirey Nov 5, 2020
2f61b30
Find signals and aggregate over search terms
madirey Nov 6, 2020
56e558a
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 9, 2020
4610643
Exclude dupes
madirey Nov 9, 2020
3fa0e69
Fixes to algorithm
madirey Nov 10, 2020
0c3c1e7
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 10, 2020
35e2119
Sync timestamps with events/signals
madirey Nov 10, 2020
29641b6
Merge branch 'master' of github.com:elastic/kibana into cidr-house-rules
madirey Nov 10, 2020
5914e99
Add timestampOverride
madirey Nov 10, 2020
26c7c0d
Merge branch 'cidr-house-rules' into threshold-dupes
madirey Nov 10, 2020
7bbd12a
Revert changes in signal creation
madirey Nov 10, 2020
399d886
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 10, 2020
960ed9b
Simplify query, return 10k buckets
madirey Nov 10, 2020
8591886
Account for when threshold.field is not supplied
madirey Nov 10, 2020
a90af11
Merge branch 'master' of github.com:elastic/kibana into cidr-house-rules
madirey Nov 10, 2020
bf910ab
Ensure we're getting the last event when threshold.field is not provided
madirey Nov 10, 2020
2396190
Merge branch 'cidr-house-rules' into threshold-dupes
madirey Nov 10, 2020
83f81cb
Add missing import
madirey Nov 10, 2020
3052975
Merge branch 'cidr-house-rules' into threshold-dupes
madirey Nov 10, 2020
8d6c81d
Handle case where threshold field not supplied
madirey Nov 10, 2020
a087823
Fix type errors
madirey Nov 11, 2020
2e424f1
Merge master
madirey Nov 11, 2020
c898480
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 11, 2020
925acc7
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 12, 2020
ae24022
Handle non-ECS fields
madirey Nov 12, 2020
2c8dcfa
Regorganize
madirey Nov 12, 2020
8a7c84a
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 13, 2020
49044a6
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 20, 2020
974a5a3
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 23, 2020
23e4ec3
Address comments
madirey Nov 23, 2020
2bc904f
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 23, 2020
1e8d122
Fix type error
madirey Nov 24, 2020
3a54495
Add unit test for buildBulkBody on threshold results
madirey Nov 24, 2020
e5f2593
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 24, 2020
80a582a
Add threshold_count back to mapping (and deprecate)
madirey Nov 25, 2020
fe956ad
Merge branch 'master' of github.com:elastic/kibana into threshold-dupes
madirey Nov 25, 2020
2c61c26
Timestamp fixes
madirey Nov 25, 2020
a855a19
Merge branch 'master' into threshold-dupes
kibanamachine Nov 30, 2020
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas';
import { singleSearchAfter } from './single_search_after';

import { AlertServices } from '../../../../../alerts/server';
import { Logger } from '../../../../../../../src/core/server';
import { SignalSearchResponse } from './types';
import { BuildRuleMessage } from './rule_messages';

interface FindPreviousThresholdSignalsParams {
from: string;
to: string;
indexPattern: string[];
services: AlertServices;
logger: Logger;
ruleId: string;
bucketByField: string;
timestampOverride: TimestampOverrideOrUndefined;
buildRuleMessage: BuildRuleMessage;
}

export const findPreviousThresholdSignals = async ({
from,
to,
indexPattern,
services,
logger,
ruleId,
bucketByField,
timestampOverride,
buildRuleMessage,
}: FindPreviousThresholdSignalsParams): Promise<{
searchResult: SignalSearchResponse;
searchDuration: string;
searchErrors: string[];
}> => {
const aggregations = {
threshold: {
terms: {
field: bucketByField ?? 'signal.rule.rule_id',
Copy link
Contributor

@marshallmain marshallmain Nov 11, 2020

Choose a reason for hiding this comment

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

If bucketByField is a non-ECS field then it won't be mapped in the signals index - in that case I don't think the aggregation will return any results.

Also the event.* fields move to original_event.* in the signals index so bucketByField will need to go through a translation before being used on the signals index remove duplicates for threshold rules on event.* fields.

},
aggs: {
lastSignalTimestamp: {
max: {
field: 'signal.original_time', // timestamp of last event captured by bucket
},
},
},
},
};

const filter = {
term: {
'signal.rule.rule_id': ruleId,
},
};

return singleSearchAfter({
aggregations,
searchAfterSortId: undefined,
timestampOverride,
index: indexPattern,
from,
to,
services,
logger,
filter,
pageSize: 0,
buildRuleMessage,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const findThresholdSignals = async ({
terms: {
field: threshold.field,
min_doc_count: threshold.value,
size: 10000, // max 10k buckets
},
aggs: {
// Get the most recent hit per bucket
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { Logger, KibanaRequest } from 'src/core/server';

import { Filter } from 'src/plugins/data/common';
import {
SIGNALS_ID,
DEFAULT_SEARCH_AFTER_PAGE_SIZE,
Expand All @@ -29,6 +30,7 @@ import {
RuleAlertAttributes,
EqlSignalSearchResponse,
BaseSignalHit,
ThresholdQueryBucket,
} from './types';
import {
getGapBetweenRuns,
Expand All @@ -46,6 +48,7 @@ import { signalParamsSchema } from './signal_params_schema';
import { siemRuleActionGroups } from './siem_rule_action_groups';
import { findMlSignals } from './find_ml_signals';
import { findThresholdSignals } from './find_threshold_signals';
import { findPreviousThresholdSignals } from './find_previous_threshold_signals';
import { bulkCreateMlSignals } from './bulk_create_ml_signals';
import { bulkCreateThresholdSignals } from './bulk_create_threshold_signals';
import {
Expand Down Expand Up @@ -300,6 +303,48 @@ export const signalRulesAlertType = ({
lists: exceptionItems ?? [],
});

const {
searchResult: previousSignals,
searchErrors: previousSearchErrors,
} = await findPreviousThresholdSignals({
indexPattern: [outputIndex],
from,
to,
services,
logger,
ruleId,
bucketByField: threshold.field,
timestampOverride,
buildRuleMessage,
});

previousSignals.aggregations.rule.threshold.buckets.forEach(
(bucket: ThresholdQueryBucket) => {
esFilter.bool.filter.push(({
bool: {
must_not: {
bool: {
must: [
{
term: {
[threshold.field ?? 'signal.rule.rule_id']: bucket.key,
},
},
{
range: {
'@timestamp': {
lte: bucket.lastSignalTimestamp.value_as_string,
},
},
},
],
},
},
},
} as unknown) as Filter);
}
);

const { searchResult: thresholdResults, searchErrors } = await findThresholdSignals({
inputIndexPattern: inputIndex,
from,
Expand Down Expand Up @@ -349,7 +394,7 @@ export const signalRulesAlertType = ({
}),
createSearchAfterReturnType({
success,
errors: [...errors, ...searchErrors],
errors: [...errors, ...previousSearchErrors, ...searchErrors],
createdSignalsCount: createdItemsCount,
bulkCreateTimes: bulkCreateDuration ? [bulkCreateDuration] : [],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,9 @@ export interface SearchAfterAndBulkCreateReturnType {
export interface ThresholdAggregationBucket extends TermAggregationBucket {
top_threshold_hits: BaseSearchResponse<SignalSource>;
}

export interface ThresholdQueryBucket extends TermAggregationBucket {
lastSignalTimestamp: {
value_as_string: string;
};
}