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][Threshold Rules] Threshold multiple aggregations with cardinality #90826

Merged
merged 50 commits into from
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a4139ce
Remove unnecessary spreads
madirey Jan 14, 2021
b73b34a
Layout, round 1
madirey Jan 14, 2021
026a6d7
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Jan 25, 2021
8c5c889
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Jan 27, 2021
ed5f9b0
Revert "Layout, round 1"
madirey Jan 28, 2021
b2f31ba
Make threshold field an array
madirey Jan 28, 2021
0842e84
Add cardinality fields
madirey Jan 28, 2021
02c3b2c
Fix validation schema
madirey Feb 1, 2021
48f6545
Query for multi-aggs
madirey Feb 1, 2021
e435241
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 1, 2021
70e4f50
Finish multi-agg aggregation
madirey Feb 1, 2021
cf7ef93
Translate to multi-agg buckets
madirey Feb 7, 2021
1ebfed0
Fix existing tests and add new test skeletons
madirey Feb 7, 2021
0786757
merge master, fix conflicts
madirey Feb 7, 2021
12a98bb
clean up
madirey Feb 9, 2021
6cba63a
Fix types
marshallmain Feb 11, 2021
78e77bc
Fix threshold_result data structure
madirey Feb 11, 2021
465c5a4
previous signals filter
madirey Feb 11, 2021
18d5363
Fix previous signal detection
madirey Feb 12, 2021
41a5ddb
Finish previous signal parsing
madirey Feb 12, 2021
19ed253
tying up loose ends
madirey Feb 14, 2021
733347c
merge master, fix conflicts
madirey Feb 15, 2021
c7eea31
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 15, 2021
319e9db
Fix timeline view for multi-agg threshold signals
madirey Feb 15, 2021
e2a7d40
Fix build_bulk_body tests
madirey Feb 15, 2021
c6abdf5
test fixes
madirey Feb 16, 2021
741c75e
Add test for threshold bucket filters
madirey Feb 16, 2021
b277b04
Address comments
madirey Feb 16, 2021
8d1e922
Fixing schema errors
madirey Feb 16, 2021
6b8c8ed
Remove unnecessary comment
madirey Feb 16, 2021
a8dc733
Fix tests
madirey Feb 16, 2021
6fd0836
Fix types
madirey Feb 16, 2021
900ead0
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 16, 2021
37956ca
linting
madirey Feb 17, 2021
7956953
linting
madirey Feb 17, 2021
af5ed84
Fixes
madirey Feb 17, 2021
ee103c1
Handle pre-7.12 threshold format in timeline view
madirey Feb 17, 2021
2094d58
missing null check
madirey Feb 17, 2021
bed1faf
adding in follow-up pr
madirey Feb 17, 2021
e979d12
Handle pre-7.12 filters
madirey Feb 17, 2021
9dab01e
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 17, 2021
3edc7f2
unnecessary change
madirey Feb 17, 2021
13821bf
Revert "unnecessary change"
madirey Feb 17, 2021
f88cf66
linting
madirey Feb 17, 2021
3e09b24
Fix rule schemas
madirey Feb 17, 2021
6eafa8d
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 17, 2021
db6dfa5
Fix tests
madirey Feb 17, 2021
9ba09b6
Merge branch 'master' of github.com:elastic/kibana into threshold-mul…
madirey Feb 18, 2021
b6fd98b
merge master, fix conflicts
madirey Feb 18, 2021
5c503fc
more fixing conflicts
madirey Feb 18, 2021
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
2 changes: 1 addition & 1 deletion x-pack/plugins/osquery/common/ecs/rule/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface RuleEcs {
tags?: string[];
threat?: unknown;
threshold?: {
field: string;
field: string | string[];
value: number;
};
type?: string[];
Expand Down
5 changes: 1 addition & 4 deletions x-pack/plugins/security_solution/common/ecs/rule/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ export interface RuleEcs {
severity?: string[];
tags?: string[];
threat?: unknown;
threshold?: {
field: string;
value: number;
};
threshold?: unknown;
type?: string[];
size?: string[];
to?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export interface SignalEcs {
group?: {
id?: string[];
};
threshold_result?: unknown;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/* eslint-disable complexity */

import dateMath from '@elastic/datemath';
import { get, getOr, isEmpty, find } from 'lodash/fp';
import { getOr, isEmpty } from 'lodash/fp';
import moment from 'moment';
import { i18n } from '@kbn/i18n';

Expand Down Expand Up @@ -131,34 +131,51 @@ export const getThresholdAggregationDataProvider = (
ecsData: Ecs,
nonEcsData: TimelineNonEcsData[]
): DataProvider[] => {
const aggregationField = ecsData.signal?.rule?.threshold?.field!;
const aggregationValue =
get(aggregationField, ecsData) ?? find(['field', aggregationField], nonEcsData)?.value;
const dataProviderValue = Array.isArray(aggregationValue)
? aggregationValue[0]
: aggregationValue;
const threshold = ecsData.signal?.rule?.threshold as string[];
const thresholdResult = JSON.parse((ecsData.signal?.threshold_result as string[])[0]);
madirey marked this conversation as resolved.
Show resolved Hide resolved

if (!dataProviderValue) {
return [];
}
const aggField = JSON.parse(threshold[0]).field;
const aggregationFields = Array.isArray(aggField) ? aggField : [aggField];

return aggregationFields.reduce<DataProvider[]>((acc, aggregationField, i) => {
const aggregationValue = thresholdResult.terms.filter(
(term) => term.field === aggregationField
)[0].value;
const dataProviderValue = Array.isArray(aggregationValue)
? aggregationValue[0]
: aggregationValue;

const aggregationFieldId = aggregationField.replace('.', '-');
if (!dataProviderValue) {
return acc;
}

return [
{
and: [],
const aggregationFieldId = aggregationField.replace('.', '-');
const dataProviderPartial = {
id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${aggregationFieldId}-${dataProviderValue}`,
name: aggregationField,
enabled: true,
excluded: false,
kqlQuery: '',
queryMatch: {
field: aggregationField,
value: dataProviderValue,
field: aggregationField as string,
value: dataProviderValue as string,
operator: ':',
},
},
];
};

if (i === 0) {
return [
...acc,
{
...dataProviderPartial,
and: [],
},
];
} else {
acc[0].and.push(dataProviderPartial);
return acc;
}
}, []);
};

export const isEqlRuleWithGroupId = (ecsData: Ecs) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface FieldValueThreshold {
field: string[];
value: string;
cardinality_field: string[];
cardinality_value: number;
cardinality_value: string;
madirey marked this conversation as resolved.
Show resolved Hide resolved
}

interface ThresholdInputProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep
field: ruleFields.threshold?.field ?? [],
value: parseInt(ruleFields.threshold?.value, 10) ?? 0,
cardinality_field: ruleFields.threshold.cardinality_field[0] ?? '',
cardinality_value: ruleFields.threshold.cardinality_value,
cardinality_value: parseInt(ruleFields.threshold?.cardinality_value, 10) ?? 0,
},
}),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ describe('buildBulkBody', () => {
delete doc._source.source;
const fakeSignalSourceHit = buildBulkBody({
doc,
ruleParams: sampleParams,
ruleParams: {
...sampleParams,
threshold: {
field: ['host.name'],
value: 100,
},
},
id: sampleRuleGuid,
name: 'rule-name',
actions: [],
Expand Down Expand Up @@ -111,7 +117,7 @@ describe('buildBulkBody', () => {
tags: ['some fake tag 1', 'some fake tag 2'],
threat: [],
threshold: {
field: 'host.name',
field: ['host.name'],
value: 100,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

this rule has type query so adding threshold fields to it makes an invalid rule. The test works for now, but may be a pain later if build_bulk_body starts using the stricter types that keep different rule types separated.

throttle: 'no_actions',
Expand Down Expand Up @@ -142,7 +148,6 @@ describe('buildBulkBody', () => {
threshold_result: {
terms: [
{
field: '',
value: 'abcd',
},
],
Expand All @@ -153,7 +158,13 @@ describe('buildBulkBody', () => {
delete doc._source.source;
const fakeSignalSourceHit = buildBulkBody({
doc,
ruleParams: sampleParams,
ruleParams: {
...sampleParams,
threshold: {
field: [],
value: 4,
},
},
id: sampleRuleGuid,
name: 'rule-name',
actions: [],
Expand Down Expand Up @@ -227,6 +238,10 @@ describe('buildBulkBody', () => {
severity_mapping: [],
tags: ['some fake tag 1', 'some fake tag 2'],
threat: [],
threshold: {
field: [],
value: 4,
},
throttle: 'no_actions',
type: 'query',
to: 'now',
Expand All @@ -240,10 +255,11 @@ describe('buildBulkBody', () => {
exceptions_list: getListArrayMock(),
},
threshold_result: {
terms: {
field: 'host.name',
value: 'abcd',
},
terms: [
{
value: 'abcd',
},
],
count: 5,
},
depth: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ describe('transformThresholdResultsToEcs', () => {
value: 'garden-gnomes',
},
],
cardinality: {
field: 'destination.ip',
value: 7,
},
cardinality: [
{
field: 'destination.ip',
value: 7,
},
],
count: 12,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ const getTransformedHits = (
value: ruleId,
},
],
// TODO: cardinality?
count: totalResults,
},
};
Expand All @@ -106,7 +105,7 @@ const getTransformedHits = (
}

const aggParts = results.aggregations && getThresholdAggregationParts(results.aggregations);
if (!aggParts.key) {
if (!aggParts) {
return [];
}

Expand All @@ -118,7 +117,7 @@ const getTransformedHits = (
if (nextLevelAggParts == null) {
throw new Error('Something went horribly wrong');
}
const nextLevelPath = `${nextLevelAggParts.name}.buckets`;
const nextLevelPath = `['${nextLevelAggParts.name}']['buckets']`;
const nextBuckets = get(nextLevelPath, bucket);
const combinations = getCombinations(nextBuckets, nextLevelIdx, nextLevelAggParts.field);
combinations.forEach((val) => {
Expand Down Expand Up @@ -205,7 +204,7 @@ const getTransformedHits = (
ruleId,
startedAt,
threshold.field as string[],
madirey marked this conversation as resolved.
Show resolved Hide resolved
bucket.terms.join(',')
bucket.terms.map((term) => term.value).join(',')
),
_source: source,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,6 @@ export const findPreviousThresholdSignals = async ({
searchDuration: string;
searchErrors: string[];
}> => {
const aggregations = {
threshold: {
terms: {
field: 'signal.threshold_result.value',
size: 10000,
},
aggs: {
lastSignalTimestamp: {
max: {
field: 'signal.original_time', // timestamp of last event captured by bucket
},
},
},
},
};

const filter = {
bool: {
must: [
Expand All @@ -80,7 +64,6 @@ export const findPreviousThresholdSignals = async ({
};

return singleSearchAfter({
aggregations,
searchAfterSortId: undefined,
timestampOverride,
index: indexPattern,
Expand All @@ -89,7 +72,7 @@ export const findPreviousThresholdSignals = async ({
services,
logger,
filter,
pageSize: 0,
pageSize: 10000, // TODO: multiple pages?
buildRuleMessage,
excludeDocsWithTimestampOverride: false,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

/*
import { loggingSystemMock } from '../../../../../../../src/core/server/mocks';
import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from './__mocks__/es_results';
import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals';
import { calculateThresholdSignalUuid } from './utils';
import { Threshold } from '../../../../common/detection_engine/schemas/common/schemas';

describe('getCombinations', () => {
it('should get all combinations for multiple aggregations without cardinality', () => {});

it('should get all combinations for multiple aggregations with cardinality', () => {});

it('should exclude empty buckets', () => {});
});
*/
Loading