mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Detection Engine] Convert wrapHits from factory to regular function (#213826)
## Summary Follow up to https://github.com/elastic/kibana/pull/212694. This PR switches more DE functions over to use `sharedParams`, an object that contains the variety of parameters that we build at the top level of rule execution in the common security rule wrapper. With `sharedParams` available throughout rule execution, it's easier to access all the parameters necessary for `wrapHits` when we call it so I also removed the "factory" logic and just call `wrapHits` directly instead of passing the function as a parameter on `sharedParams`. There should be very few behavior changes in the code as a result of this PR. - `kibana.alert.rule.indices` is now populated for ES|QL alerts - `ignoreFields` and `ignoreFieldsRegexes` are now respected by EQL, new terms, and all suppressed rule types --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
02409dbd65
commit
920ce1b9ea
35 changed files with 419 additions and 914 deletions
|
@ -38,7 +38,6 @@ export const getSharedParamsMock = <T extends RuleParams = QueryRuleParams>({
|
|||
},
|
||||
searchAfterSize: 100,
|
||||
bulkCreate: jest.fn(),
|
||||
wrapHits: jest.fn(),
|
||||
ruleDataClient: createRuleDataClientMock(),
|
||||
runtimeMappings: undefined,
|
||||
aggregatableTimestampField: '@timestamp',
|
||||
|
@ -46,5 +45,7 @@ export const getSharedParamsMock = <T extends RuleParams = QueryRuleParams>({
|
|||
exceptionFilter: undefined,
|
||||
alertWithSuppression: jest.fn(),
|
||||
refreshOnIndexingAlerts: false,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
...rewrites,
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ import { getNotificationResultsLink } from '../rule_actions_legacy';
|
|||
// eslint-disable-next-line no-restricted-imports
|
||||
import { formatAlertForNotificationActions } from '../rule_actions_legacy/logic/notifications/schedule_notification_actions';
|
||||
import { createResultObject } from './utils';
|
||||
import { bulkCreateFactory, wrapHitsFactory } from './factories';
|
||||
import { bulkCreateFactory } from './factories';
|
||||
import { RuleExecutionStatusEnum } from '../../../../common/api/detection_engine/rule_monitoring';
|
||||
import { truncateList } from '../rule_monitoring';
|
||||
import aadFieldConversion from '../routes/index/signal_aad_mapping.json';
|
||||
|
@ -421,18 +421,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
});
|
||||
|
||||
const intendedTimestamp = startedAtOverridden ? startedAt : undefined;
|
||||
const wrapHits = wrapHitsFactory({
|
||||
ignoreFields: ignoreFieldsObject,
|
||||
ignoreFieldsRegexes,
|
||||
mergeStrategy,
|
||||
completeRule,
|
||||
spaceId,
|
||||
indicesToQuery: inputIndex,
|
||||
alertTimestampOverride,
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
const { filter: exceptionFilter, unprocessedExceptions } = await buildExceptionFilter({
|
||||
startedAt,
|
||||
|
@ -461,7 +449,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
searchAfterSize,
|
||||
tuple,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
listClient,
|
||||
ruleDataClient,
|
||||
mergeStrategy,
|
||||
|
@ -476,6 +463,8 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
experimentalFeatures,
|
||||
intendedTimestamp,
|
||||
spaceId,
|
||||
ignoreFields: ignoreFieldsObject,
|
||||
ignoreFieldsRegexes,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -76,7 +76,6 @@ export const buildAlertGroupFromSequence = ({
|
|||
const {
|
||||
alertTimestampOverride,
|
||||
intendedTimestamp,
|
||||
mergeStrategy,
|
||||
completeRule,
|
||||
spaceId,
|
||||
inputIndex: indicesToQuery,
|
||||
|
@ -96,21 +95,11 @@ export const buildAlertGroupFromSequence = ({
|
|||
try {
|
||||
baseAlerts = sequence.events.map((event) =>
|
||||
transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: event,
|
||||
mergeStrategy,
|
||||
// TODO: respect ignore fields
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: false,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: 'placeholder-alert-uuid', // This is overriden below
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
|
@ -53,6 +53,7 @@ import { isEqlSequenceQuery } from '../../../../../common/detection_engine/utils
|
|||
import { logShardFailures } from '../utils/log_shard_failure';
|
||||
import { checkErrorDetails } from '../utils/check_error_details';
|
||||
import { wrapSequences } from './wrap_sequences';
|
||||
import { wrapHits } from '../factories';
|
||||
|
||||
interface EqlExecutorParams {
|
||||
sharedParams: SecuritySharedParams<EqlRuleParams>;
|
||||
|
@ -76,7 +77,7 @@ export const eqlExecutor = async ({
|
|||
result: SearchAfterAndBulkCreateReturnType;
|
||||
loggedRequests?: RulePreviewLoggedRequest[];
|
||||
}> => {
|
||||
const { completeRule, tuple, ruleExecutionLogger, bulkCreate, wrapHits } = sharedParams;
|
||||
const { completeRule, tuple, ruleExecutionLogger, bulkCreate } = sharedParams;
|
||||
const ruleParams = completeRule.ruleParams;
|
||||
const isLoggedRequestsEnabled = state?.isLoggedRequestsEnabled ?? false;
|
||||
const loggedRequests: RulePreviewLoggedRequest[] = [];
|
||||
|
@ -157,7 +158,7 @@ export const eqlExecutor = async ({
|
|||
experimentalFeatures,
|
||||
});
|
||||
} else {
|
||||
newSignals = wrapHits(events, buildReasonMessageForEqlAlert);
|
||||
newSignals = wrapHits(sharedParams, events, buildReasonMessageForEqlAlert);
|
||||
}
|
||||
} else if (sequences) {
|
||||
if (
|
||||
|
|
|
@ -71,11 +71,6 @@ export const esqlExecutor = async ({
|
|||
exceptionFilter,
|
||||
unprocessedExceptions,
|
||||
ruleExecutionLogger,
|
||||
spaceId,
|
||||
mergeStrategy,
|
||||
alertTimestampOverride,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
bulkCreate,
|
||||
} = sharedParams;
|
||||
const loggedRequests: RulePreviewLoggedRequest[] = [];
|
||||
|
@ -168,21 +163,6 @@ export const esqlExecutor = async ({
|
|||
completeRule.ruleParams.query
|
||||
);
|
||||
|
||||
const wrapHits = (events: Array<estypes.SearchHit<SignalSource>>) =>
|
||||
wrapEsqlAlerts({
|
||||
events,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
isRuleAggregating,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple,
|
||||
intendedTimestamp,
|
||||
expandedFields,
|
||||
});
|
||||
|
||||
const syntheticHits: Array<estypes.SearchHit<SignalSource>> = results.map((document) => {
|
||||
const { _id, _version, _index, ...esqlResult } = document;
|
||||
|
||||
|
@ -202,18 +182,9 @@ export const esqlExecutor = async ({
|
|||
if (isAlertSuppressionActive) {
|
||||
const wrapSuppressedHits = (events: Array<estypes.SearchHit<SignalSource>>) =>
|
||||
wrapSuppressedEsqlAlerts({
|
||||
sharedParams,
|
||||
events,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
isRuleAggregating,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
tuple,
|
||||
intendedTimestamp,
|
||||
expandedFields,
|
||||
});
|
||||
|
||||
|
@ -240,7 +211,12 @@ export const esqlExecutor = async ({
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
const wrappedAlerts = wrapHits(syntheticHits);
|
||||
const wrappedAlerts = wrapEsqlAlerts({
|
||||
sharedParams,
|
||||
events: syntheticHits,
|
||||
isRuleAggregating,
|
||||
expandedFields,
|
||||
});
|
||||
|
||||
const enrichAlerts = createEnrichEventsFunction({
|
||||
services,
|
||||
|
|
|
@ -4,47 +4,29 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { cloneDeep } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { ALERT_UUID } from '@kbn/rule-data-utils';
|
||||
import { getCompleteRuleMock, getEsqlRuleParams } from '../../rule_schema/mocks';
|
||||
import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
|
||||
import { getEsqlRuleParams } from '../../rule_schema/mocks';
|
||||
import { sampleDocNoSortIdWithTimestamp } from '../__mocks__/es_results';
|
||||
import { wrapEsqlAlerts } from './wrap_esql_alerts';
|
||||
|
||||
import * as esqlUtils from './utils/generate_alert_id';
|
||||
|
||||
const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
|
||||
import { getSharedParamsMock } from '../__mocks__/shared_params';
|
||||
|
||||
const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
|
||||
const publicBaseUrl = 'http://somekibanabaseurl.com';
|
||||
|
||||
const alertSuppression = {
|
||||
groupBy: ['source.ip'],
|
||||
};
|
||||
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
completeRule.ruleParams.alertSuppression = alertSuppression;
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
});
|
||||
|
||||
describe('wrapSuppressedEsqlAlerts', () => {
|
||||
test('should create an alert with the correct _id from a document', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapEsqlAlerts({
|
||||
sharedParams,
|
||||
events: [doc],
|
||||
isRuleAggregating: false,
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('ed7fbf575371c898e0f0aea48cdf0bf1865939a9');
|
||||
|
@ -53,26 +35,19 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
|
||||
test('should call generateAlertId for alert id', () => {
|
||||
jest.spyOn(esqlUtils, 'generateAlertId').mockReturnValueOnce('mocked-alert-id');
|
||||
const completeRuleCloned = cloneDeep(completeRule);
|
||||
completeRuleCloned.ruleParams.alertSuppression = {
|
||||
groupBy: ['someKey'],
|
||||
};
|
||||
const newSharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams({
|
||||
alertSuppression: {
|
||||
groupBy: ['someKey'],
|
||||
},
|
||||
}),
|
||||
});
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapEsqlAlerts({
|
||||
sharedParams: newSharedParams,
|
||||
events: [doc],
|
||||
spaceId: 'default',
|
||||
isRuleAggregating: true,
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule: completeRuleCloned,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('mocked-alert-id');
|
||||
|
@ -87,7 +62,7 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
spaceId: 'default',
|
||||
tuple: {
|
||||
from: expect.any(Object),
|
||||
maxSignals: 100,
|
||||
maxSignals: 10000,
|
||||
to: expect.any(Object),
|
||||
},
|
||||
})
|
||||
|
@ -99,20 +74,10 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
const doc2 = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d72');
|
||||
const doc3 = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d73');
|
||||
const alerts = wrapEsqlAlerts({
|
||||
sharedParams,
|
||||
events: [doc1, doc1, doc2, doc2, doc3],
|
||||
isRuleAggregating: false,
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts).toHaveLength(3);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Moment } from 'moment';
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { uniqBy } from 'lodash';
|
||||
|
||||
|
@ -13,69 +12,40 @@ import type {
|
|||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { CompleteRule, EsqlRuleParams } from '../../rule_schema';
|
||||
import type { EsqlRuleParams } from '../../rule_schema';
|
||||
import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
|
||||
import type { SignalSource } from '../types';
|
||||
import type { SecuritySharedParams, SignalSource } from '../types';
|
||||
import { generateAlertId } from './utils';
|
||||
|
||||
export const wrapEsqlAlerts = ({
|
||||
sharedParams,
|
||||
events,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple,
|
||||
isRuleAggregating,
|
||||
intendedTimestamp,
|
||||
expandedFields,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams<EsqlRuleParams>;
|
||||
isRuleAggregating: boolean;
|
||||
events: Array<estypes.SearchHit<SignalSource>>;
|
||||
spaceId: string | null | undefined;
|
||||
completeRule: CompleteRule<EsqlRuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
tuple: {
|
||||
to: Moment;
|
||||
from: Moment;
|
||||
maxSignals: number;
|
||||
};
|
||||
intendedTimestamp: Date | undefined;
|
||||
expandedFields?: string[];
|
||||
expandedFields: string[] | undefined;
|
||||
}): Array<WrappedFieldsLatest<BaseFieldsLatest>> => {
|
||||
const wrapped = events.map<WrappedFieldsLatest<BaseFieldsLatest>>((event, i) => {
|
||||
const id = generateAlertId({
|
||||
event,
|
||||
spaceId,
|
||||
completeRule,
|
||||
tuple,
|
||||
spaceId: sharedParams.spaceId,
|
||||
completeRule: sharedParams.completeRule,
|
||||
tuple: sharedParams.tuple,
|
||||
isRuleAggregating,
|
||||
index: i,
|
||||
expandedFields,
|
||||
});
|
||||
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageForNewTermsAlert,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { cloneDeep } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
ALERT_URL,
|
||||
|
@ -16,14 +14,12 @@ import {
|
|||
ALERT_SUPPRESSION_START,
|
||||
ALERT_SUPPRESSION_END,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { getCompleteRuleMock, getEsqlRuleParams } from '../../rule_schema/mocks';
|
||||
import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
|
||||
import { getEsqlRuleParams } from '../../rule_schema/mocks';
|
||||
import { sampleDocNoSortIdWithTimestamp } from '../__mocks__/es_results';
|
||||
import { wrapSuppressedEsqlAlerts } from './wrap_suppressed_esql_alerts';
|
||||
|
||||
import * as esqlUtils from './utils/generate_alert_id';
|
||||
|
||||
const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
|
||||
import { getSharedParamsMock } from '../__mocks__/shared_params';
|
||||
|
||||
const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
|
||||
const publicBaseUrl = 'http://somekibanabaseurl.com';
|
||||
|
@ -32,28 +28,19 @@ const alertSuppression = {
|
|||
groupBy: ['source.ip'],
|
||||
};
|
||||
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
completeRule.ruleParams.alertSuppression = alertSuppression;
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams({ alertSuppression }),
|
||||
rewrites: { publicBaseUrl },
|
||||
});
|
||||
|
||||
describe('wrapSuppressedEsqlAlerts', () => {
|
||||
test('should create an alert with the correct _id from a document and suppression fields', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedEsqlAlerts({
|
||||
sharedParams,
|
||||
events: [doc],
|
||||
isRuleAggregating: false,
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('ed7fbf575371c898e0f0aea48cdf0bf1865939a9');
|
||||
|
@ -73,27 +60,20 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
});
|
||||
|
||||
test('should create an alert with a different _id if suppression field is different', () => {
|
||||
const completeRuleCloned = cloneDeep(completeRule);
|
||||
completeRuleCloned.ruleParams.alertSuppression = {
|
||||
groupBy: ['someKey'],
|
||||
};
|
||||
const newSharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams({
|
||||
alertSuppression: {
|
||||
groupBy: ['someKey'],
|
||||
},
|
||||
}),
|
||||
rewrites: { publicBaseUrl },
|
||||
});
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedEsqlAlerts({
|
||||
sharedParams: newSharedParams,
|
||||
events: [doc],
|
||||
spaceId: 'default',
|
||||
isRuleAggregating: true,
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule: completeRuleCloned,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._source[ALERT_URL]).toContain(
|
||||
|
@ -110,27 +90,12 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
|
||||
test('should call generateAlertId for alert id', () => {
|
||||
jest.spyOn(esqlUtils, 'generateAlertId').mockReturnValueOnce('mocked-alert-id');
|
||||
const completeRuleCloned = cloneDeep(completeRule);
|
||||
completeRuleCloned.ruleParams.alertSuppression = {
|
||||
groupBy: ['someKey'],
|
||||
};
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedEsqlAlerts({
|
||||
sharedParams,
|
||||
events: [doc],
|
||||
spaceId: 'default',
|
||||
isRuleAggregating: false,
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule: completeRuleCloned,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('mocked-alert-id');
|
||||
|
@ -145,7 +110,7 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
spaceId: 'default',
|
||||
tuple: {
|
||||
from: expect.any(Object),
|
||||
maxSignals: 100,
|
||||
maxSignals: 10000,
|
||||
to: expect.any(Object),
|
||||
},
|
||||
})
|
||||
|
@ -156,21 +121,10 @@ describe('wrapSuppressedEsqlAlerts', () => {
|
|||
const doc1 = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const doc2 = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d72');
|
||||
const alerts = wrapSuppressedEsqlAlerts({
|
||||
sharedParams,
|
||||
events: [doc1, doc1, doc2],
|
||||
isRuleAggregating: false,
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
tuple: {
|
||||
to: moment('2010-10-20 04:43:12'),
|
||||
from: moment('2010-10-20 04:43:12'),
|
||||
maxSignals: 100,
|
||||
},
|
||||
intendedTimestamp: undefined,
|
||||
expandedFields: undefined,
|
||||
});
|
||||
|
||||
expect(alerts).toHaveLength(2);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { uniqBy } from 'lodash';
|
||||
import objectHash from 'object-hash';
|
||||
import type { Moment } from 'moment';
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas';
|
||||
|
@ -16,48 +15,25 @@ import type {
|
|||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { CompleteRule, EsqlRuleParams } from '../../rule_schema';
|
||||
import type { EsqlRuleParams } from '../../rule_schema';
|
||||
import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
|
||||
import type { SignalSource } from '../types';
|
||||
import type { SecuritySharedParams, SignalSource } from '../types';
|
||||
import { getSuppressionAlertFields, getSuppressionTerms } from '../utils';
|
||||
import { generateAlertId } from './utils';
|
||||
|
||||
export const wrapSuppressedEsqlAlerts = ({
|
||||
sharedParams,
|
||||
events,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
tuple,
|
||||
isRuleAggregating,
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
intendedTimestamp,
|
||||
expandedFields,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams<EsqlRuleParams>;
|
||||
isRuleAggregating: boolean;
|
||||
events: Array<estypes.SearchHit<SignalSource>>;
|
||||
spaceId: string | null | undefined;
|
||||
completeRule: CompleteRule<EsqlRuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
tuple: {
|
||||
to: Moment;
|
||||
from: Moment;
|
||||
maxSignals: number;
|
||||
};
|
||||
primaryTimestamp: string;
|
||||
secondaryTimestamp?: string;
|
||||
intendedTimestamp: Date | undefined;
|
||||
expandedFields?: string[];
|
||||
expandedFields: string[] | undefined;
|
||||
}): Array<WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest>> => {
|
||||
const { spaceId, completeRule, tuple, primaryTimestamp, secondaryTimestamp } = sharedParams;
|
||||
const wrapped = events.map<WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest>>(
|
||||
(event, i) => {
|
||||
const combinedFields = { ...event?.fields, ...event._source };
|
||||
|
@ -80,20 +56,11 @@ export const wrapSuppressedEsqlAlerts = ({
|
|||
const instanceId = objectHash([suppressionTerms, completeRule.alertId, spaceId]);
|
||||
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageForNewTermsAlert,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
*/
|
||||
|
||||
export * from './bulk_create_factory';
|
||||
export * from './wrap_hits_factory';
|
||||
export * from './wrap_hits';
|
||||
export * from '../eql/wrap_sequences';
|
||||
|
|
|
@ -45,13 +45,10 @@ import {
|
|||
} from '../../../../../../common/field_maps/field_names';
|
||||
|
||||
import { transformHitToAlert } from './transform_hit_to_alert';
|
||||
import {
|
||||
getCompleteRuleMock,
|
||||
getEsqlRuleParams,
|
||||
getQueryRuleParams,
|
||||
} from '../../../rule_schema/mocks';
|
||||
import { getEsqlRuleParams, getQueryRuleParams } from '../../../rule_schema/mocks';
|
||||
import { ruleExecutionLogMock } from '../../../rule_monitoring/mocks';
|
||||
import { get } from 'lodash';
|
||||
import { getSharedParamsMock } from '../../__mocks__/shared_params';
|
||||
|
||||
const SPACE_ID = 'space';
|
||||
const publicBaseUrl = 'testKibanaBasePath.com';
|
||||
|
@ -66,23 +63,21 @@ describe('transformHitToAlert', () => {
|
|||
'event.action': 'process',
|
||||
'event.action.keyword': 'process',
|
||||
});
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alert['kibana.alert.original_event.action']).toEqual('process');
|
||||
|
@ -99,23 +94,23 @@ describe('transformHitToAlert', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(get(alert.event, 'kind')).toEqual(undefined);
|
||||
|
@ -130,23 +125,23 @@ describe('transformHitToAlert', () => {
|
|||
'event.kind': 'test-value',
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(get(alert.event, 'kind')).toEqual(undefined);
|
||||
|
@ -161,23 +156,23 @@ describe('transformHitToAlert', () => {
|
|||
testField: 'testValue',
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alert.event).toEqual(undefined);
|
||||
|
@ -192,23 +187,23 @@ describe('transformHitToAlert', () => {
|
|||
'event.action': ['process', { objectSubfield: 'test' }],
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getEsqlRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getEsqlRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alert['kibana.alert.original_event.action']).toEqual(['process']);
|
||||
|
@ -216,22 +211,23 @@ describe('transformHitToAlert', () => {
|
|||
|
||||
it('builds an alert as expected without original_event if event does not exist', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71');
|
||||
const completeRule = getCompleteRuleMock(getQueryRuleParams());
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getQueryRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
},
|
||||
});
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
delete doc._source.event;
|
||||
|
||||
|
@ -329,7 +325,7 @@ describe('transformHitToAlert', () => {
|
|||
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
|
||||
investigation_fields: undefined,
|
||||
},
|
||||
[ALERT_RULE_INDICES]: completeRule.ruleParams.index,
|
||||
[ALERT_RULE_INDICES]: sharedParams.completeRule.ruleParams.index,
|
||||
...flattenWithPrefix(ALERT_RULE_NAMESPACE, {
|
||||
actions: [],
|
||||
author: ['Elastic'],
|
||||
|
@ -418,22 +414,21 @@ describe('transformHitToAlert', () => {
|
|||
[EVENT_MODULE]: 'system',
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getQueryRuleParams());
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getQueryRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
},
|
||||
});
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
const expected = {
|
||||
...alert,
|
||||
|
@ -459,22 +454,22 @@ describe('transformHitToAlert', () => {
|
|||
[EVENT_MODULE]: 'system',
|
||||
},
|
||||
};
|
||||
const completeRule = getCompleteRuleMock(getQueryRuleParams());
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getQueryRuleParams(),
|
||||
rewrites: {
|
||||
spaceId: SPACE_ID,
|
||||
mergeStrategy: 'missingFields',
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
intendedTimestamp: new Date('2019-01-01T00:00:00.000Z'),
|
||||
},
|
||||
});
|
||||
const alert = transformHitToAlert({
|
||||
spaceId: SPACE_ID,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageStub,
|
||||
indicesToQuery: [],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: new Date('2019-01-01T00:00:00.000Z'),
|
||||
});
|
||||
const expected = {
|
||||
...alert,
|
||||
|
|
|
@ -11,13 +11,10 @@ import { requiredOptional } from '@kbn/zod-helpers';
|
|||
import { EVENT_KIND } from '@kbn/rule-data-utils';
|
||||
|
||||
import type { BaseHit } from '../../../../../../common/detection_engine/types';
|
||||
import type { ConfigType } from '../../../../../config';
|
||||
import type { BuildReasonMessage } from '../../utils/reason_formatters';
|
||||
import { getMergeStrategy } from '../../utils/source_fields_merging/strategies';
|
||||
import type { SignalSource, SignalSourceHit } from '../../types';
|
||||
import type { SecuritySharedParams, SignalSource, SignalSourceHit } from '../../types';
|
||||
import { buildAlertFields, isThresholdResult } from './build_alert';
|
||||
import type { CompleteRule, RuleParams } from '../../../rule_schema';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../../rule_monitoring';
|
||||
import { buildRuleNameFromMapping } from '../../utils/mappings/build_rule_name_from_mapping';
|
||||
import { buildSeverityFromMapping } from '../../utils/mappings/build_severity_from_mapping';
|
||||
import { buildRiskScoreFromMapping } from '../../utils/mappings/build_risk_score_from_mapping';
|
||||
|
@ -31,20 +28,11 @@ const isSourceDoc = (hit: SignalSourceHit): hit is BaseHit<SignalSource> => {
|
|||
};
|
||||
|
||||
export interface TransformHitToAlertProps {
|
||||
spaceId: string | null | undefined;
|
||||
completeRule: CompleteRule<RuleParams>;
|
||||
sharedParams: SecuritySharedParams;
|
||||
doc: estypes.SearchHit<SignalSource>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
ignoreFields: Record<string, boolean>;
|
||||
ignoreFieldsRegexes: string[];
|
||||
applyOverrides: boolean;
|
||||
buildReasonMessage: BuildReasonMessage;
|
||||
indicesToQuery: string[];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
alertUuid: string;
|
||||
publicBaseUrl?: string;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,23 +45,27 @@ export interface TransformHitToAlertProps {
|
|||
* @returns The body that can be added to a bulk call for inserting the signal.
|
||||
*/
|
||||
export const transformHitToAlert = ({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc,
|
||||
mergeStrategy,
|
||||
ignoreFields,
|
||||
ignoreFieldsRegexes,
|
||||
applyOverrides,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
}: TransformHitToAlertProps): BaseFieldsLatest => {
|
||||
const mergedDoc = getMergeStrategy(mergeStrategy)({ doc, ignoreFields, ignoreFieldsRegexes });
|
||||
const mergedDoc = getMergeStrategy(sharedParams.mergeStrategy)({
|
||||
doc,
|
||||
ignoreFields: sharedParams.ignoreFields,
|
||||
ignoreFieldsRegexes: sharedParams.ignoreFieldsRegexes,
|
||||
});
|
||||
const thresholdResult = mergedDoc._source?.threshold_result;
|
||||
const {
|
||||
completeRule,
|
||||
spaceId,
|
||||
inputIndex: indicesToQuery,
|
||||
publicBaseUrl,
|
||||
alertTimestampOverride,
|
||||
intendedTimestamp,
|
||||
ruleExecutionLogger,
|
||||
} = sharedParams;
|
||||
|
||||
if (isSourceDoc(mergedDoc)) {
|
||||
const overrides = applyOverrides
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 type { estypes } from '@elastic/elasticsearch';
|
||||
import type { SecuritySharedParams, SignalSource, SimpleHit } from '../types';
|
||||
import { generateId } from '../utils/utils';
|
||||
import { transformHitToAlert } from './utils/transform_hit_to_alert';
|
||||
import type { BuildReasonMessage } from '../utils/reason_formatters';
|
||||
import type {
|
||||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
|
||||
/**
|
||||
* wrapHits is responsible for turning source events into alerts. Since we copy the source data into the alert, we are
|
||||
* effectively "wrapping" the source hits by adding alert metadata to them in `kibana.alert.*` fields and
|
||||
* generating an _id for the alert,
|
||||
* @param sharedParams SecuritySharedParams passed in from the common security rule wrapper logic
|
||||
* @param events Source events to turn into alerts
|
||||
* @param buildReasonMessage Function to generate the reason message based on source data
|
||||
* @returns Alerts ready to index
|
||||
*/
|
||||
export const wrapHits = (
|
||||
sharedParams: SecuritySharedParams,
|
||||
events: Array<estypes.SearchHit<SignalSource>>,
|
||||
buildReasonMessage: BuildReasonMessage
|
||||
): Array<WrappedFieldsLatest<BaseFieldsLatest>> => {
|
||||
const wrappedDocs = events.map((event): WrappedFieldsLatest<BaseFieldsLatest> => {
|
||||
const id = generateId(
|
||||
event._index,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
event._id!,
|
||||
String(event._version),
|
||||
`${sharedParams.spaceId}:${sharedParams.completeRule.alertId}`
|
||||
);
|
||||
|
||||
const baseAlert = transformHitToAlert({
|
||||
sharedParams,
|
||||
doc: event as SimpleHit,
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
alertUuid: id,
|
||||
});
|
||||
|
||||
return {
|
||||
_id: id,
|
||||
_index: '',
|
||||
_source: {
|
||||
...baseAlert,
|
||||
},
|
||||
};
|
||||
});
|
||||
return wrappedDocs.filter(
|
||||
(doc) =>
|
||||
!doc._source['kibana.alert.ancestors'].some(
|
||||
(ancestor) => ancestor.rule === sharedParams.completeRule.alertId
|
||||
)
|
||||
);
|
||||
};
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* 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 type { estypes } from '@elastic/elasticsearch';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { SignalSource, SimpleHit } from '../types';
|
||||
import type { CompleteRule, RuleParams } from '../../rule_schema';
|
||||
import { generateId } from '../utils/utils';
|
||||
import { transformHitToAlert } from './utils/transform_hit_to_alert';
|
||||
import type { BuildReasonMessage } from '../utils/reason_formatters';
|
||||
import type {
|
||||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
|
||||
export const wrapHitsFactory =
|
||||
({
|
||||
completeRule,
|
||||
ignoreFields,
|
||||
ignoreFieldsRegexes,
|
||||
mergeStrategy,
|
||||
spaceId,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
publicBaseUrl,
|
||||
ruleExecutionLogger,
|
||||
intendedTimestamp,
|
||||
}: {
|
||||
completeRule: CompleteRule<RuleParams>;
|
||||
ignoreFields: Record<string, boolean>;
|
||||
ignoreFieldsRegexes: string[];
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
spaceId: string | null | undefined;
|
||||
indicesToQuery: string[];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
publicBaseUrl: string | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}) =>
|
||||
(
|
||||
events: Array<estypes.SearchHit<SignalSource>>,
|
||||
buildReasonMessage: BuildReasonMessage
|
||||
): Array<WrappedFieldsLatest<BaseFieldsLatest>> => {
|
||||
const wrappedDocs = events.map((event): WrappedFieldsLatest<BaseFieldsLatest> => {
|
||||
const id = generateId(
|
||||
event._index,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
event._id!,
|
||||
String(event._version),
|
||||
`${spaceId}:${completeRule.alertId}`
|
||||
);
|
||||
|
||||
const baseAlert = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
doc: event as SimpleHit,
|
||||
mergeStrategy,
|
||||
ignoreFields,
|
||||
ignoreFieldsRegexes,
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
_id: id,
|
||||
_index: '',
|
||||
_source: {
|
||||
...baseAlert,
|
||||
},
|
||||
};
|
||||
});
|
||||
return wrappedDocs.filter(
|
||||
(doc) =>
|
||||
!doc._source['kibana.alert.ancestors'].some(
|
||||
(ancestor) => ancestor.rule === completeRule.alertId
|
||||
)
|
||||
);
|
||||
};
|
|
@ -13,26 +13,14 @@ import type {
|
|||
AlertInstanceState,
|
||||
RuleExecutorServices,
|
||||
} from '@kbn/alerting-plugin/server';
|
||||
import type { GenericBulkCreateResponse } from '../factories';
|
||||
import { wrapHits, type GenericBulkCreateResponse } from '../factories';
|
||||
import type { Anomaly } from '../../../machine_learning';
|
||||
import type { BulkCreate, WrapHits } from '../types';
|
||||
import type { CompleteRule, MachineLearningRuleParams } from '../../rule_schema';
|
||||
import type { SecuritySharedParams } from '../types';
|
||||
import type { MachineLearningRuleParams } from '../../rule_schema';
|
||||
import { buildReasonMessageForMlAlert } from '../utils/reason_formatters';
|
||||
import type { BaseFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { createEnrichEventsFunction } from '../utils/enrichments';
|
||||
|
||||
interface BulkCreateMlSignalsParams {
|
||||
anomalyHits: Array<estypes.SearchHit<Anomaly>>;
|
||||
completeRule: CompleteRule<MachineLearningRuleParams>;
|
||||
services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
id: string;
|
||||
signalsIndex: string;
|
||||
bulkCreate: BulkCreate;
|
||||
wrapHits: WrapHits;
|
||||
}
|
||||
|
||||
interface EcsAnomaly extends Anomaly {
|
||||
'@timestamp': string;
|
||||
}
|
||||
|
@ -75,19 +63,24 @@ const transformAnomalyResultsToEcs = (
|
|||
}));
|
||||
};
|
||||
|
||||
export const bulkCreateMlSignals = async (
|
||||
params: BulkCreateMlSignalsParams
|
||||
): Promise<GenericBulkCreateResponse<BaseFieldsLatest>> => {
|
||||
const anomalyResults = params.anomalyHits;
|
||||
const ecsResults = transformAnomalyResultsToEcs(anomalyResults);
|
||||
export const bulkCreateMlSignals = async ({
|
||||
sharedParams,
|
||||
anomalyHits,
|
||||
services,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams<MachineLearningRuleParams>;
|
||||
anomalyHits: Array<estypes.SearchHit<Anomaly>>;
|
||||
services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>;
|
||||
}): Promise<GenericBulkCreateResponse<BaseFieldsLatest>> => {
|
||||
const ecsResults = transformAnomalyResultsToEcs(anomalyHits);
|
||||
|
||||
const wrappedDocs = params.wrapHits(ecsResults, buildReasonMessageForMlAlert);
|
||||
return params.bulkCreate(
|
||||
const wrappedDocs = wrapHits(sharedParams, ecsResults, buildReasonMessageForMlAlert);
|
||||
return sharedParams.bulkCreate(
|
||||
wrappedDocs,
|
||||
undefined,
|
||||
createEnrichEventsFunction({
|
||||
services: params.services,
|
||||
logger: params.ruleExecutionLogger,
|
||||
services,
|
||||
logger: sharedParams.ruleExecutionLogger,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -62,8 +62,6 @@ export const mlExecutor = async ({
|
|||
exceptionFilter,
|
||||
listClient,
|
||||
unprocessedExceptions,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
} = sharedParams;
|
||||
const result = createSearchAfterReturnType();
|
||||
const ruleParams = completeRule.ruleParams;
|
||||
|
@ -166,14 +164,9 @@ export const mlExecutor = async ({
|
|||
});
|
||||
} else {
|
||||
const createResult = await bulkCreateMlSignals({
|
||||
sharedParams,
|
||||
anomalyHits: filteredAnomalyHits,
|
||||
completeRule,
|
||||
services,
|
||||
ruleExecutionLogger,
|
||||
id: completeRule.alertId,
|
||||
signalsIndex: ruleParams.outputIndex,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
});
|
||||
addToSearchAfterReturn({ current: result, next: createResult });
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas';
|
||||
import type {
|
||||
SearchAfterAndBulkCreateParams,
|
||||
SearchAfterAndBulkCreateReturnType,
|
||||
SecuritySharedParams,
|
||||
WrapSuppressedHits,
|
||||
} from '../types';
|
||||
|
||||
|
@ -20,29 +20,21 @@ import { executeBulkCreateAlerts } from '../utils/bulk_create_suppressed_alerts_
|
|||
import type {
|
||||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
NewTermsFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import { partitionMissingFieldsEvents } from '../utils/partition_missing_fields_events';
|
||||
import type { EventsAndTerms } from './types';
|
||||
import type { ExperimentalFeatures } from '../../../../../common';
|
||||
import { wrapNewTermsAlerts } from './wrap_new_terms_alerts';
|
||||
import { wrapSuppressedNewTermsAlerts } from './wrap_suppressed_new_terms_alerts';
|
||||
import type { NewTermsRuleParams } from '../../rule_schema';
|
||||
|
||||
interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndBulkCreateParams {
|
||||
wrapSuppressedHits: WrapSuppressedHits;
|
||||
alertSuppression?: AlertSuppressionCamel;
|
||||
}
|
||||
export interface BulkCreateSuppressedAlertsParams
|
||||
extends Pick<
|
||||
SearchAfterAndBulkCreateSuppressedAlertsParams,
|
||||
'sharedParams' | 'services' | 'alertSuppression'
|
||||
> {
|
||||
wrapHits: (
|
||||
events: EventsAndTerms[]
|
||||
) => Array<WrappedFieldsLatest<BaseFieldsLatest & NewTermsFieldsLatest>>;
|
||||
wrapSuppressedHits: (
|
||||
events: EventsAndTerms[]
|
||||
) => Array<
|
||||
WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest & NewTermsFieldsLatest>
|
||||
>;
|
||||
extends Pick<SearchAfterAndBulkCreateSuppressedAlertsParams, 'services' | 'alertSuppression'> {
|
||||
sharedParams: SecuritySharedParams<NewTermsRuleParams>;
|
||||
eventsAndTerms: EventsAndTerms[];
|
||||
toReturn: SearchAfterAndBulkCreateReturnType;
|
||||
experimentalFeatures: ExperimentalFeatures;
|
||||
|
@ -56,8 +48,6 @@ export interface BulkCreateSuppressedAlertsParams
|
|||
export const bulkCreateSuppressedNewTermsAlertsInMemory = async ({
|
||||
sharedParams,
|
||||
eventsAndTerms,
|
||||
wrapHits,
|
||||
wrapSuppressedHits,
|
||||
toReturn,
|
||||
services,
|
||||
alertSuppression,
|
||||
|
@ -77,12 +67,18 @@ export const bulkCreateSuppressedNewTermsAlertsInMemory = async ({
|
|||
['event', 'fields']
|
||||
);
|
||||
|
||||
unsuppressibleWrappedDocs = wrapHits(partitionedEvents[1]);
|
||||
unsuppressibleWrappedDocs = wrapNewTermsAlerts({
|
||||
sharedParams,
|
||||
eventsAndTerms: partitionedEvents[1],
|
||||
});
|
||||
|
||||
suppressibleEvents = partitionedEvents[0];
|
||||
}
|
||||
|
||||
const suppressibleWrappedDocs = wrapSuppressedHits(suppressibleEvents);
|
||||
const suppressibleWrappedDocs = wrapSuppressedNewTermsAlerts({
|
||||
sharedParams,
|
||||
eventsAndTerms: suppressibleEvents,
|
||||
});
|
||||
|
||||
return executeBulkCreateAlerts({
|
||||
sharedParams,
|
||||
|
|
|
@ -16,7 +16,6 @@ import type { CreateRuleOptions, SecurityAlertType } from '../types';
|
|||
import { singleSearchAfter } from '../utils/single_search_after';
|
||||
import { getFilter } from '../utils/get_filter';
|
||||
import { wrapNewTermsAlerts } from './wrap_new_terms_alerts';
|
||||
import { wrapSuppressedNewTermsAlerts } from './wrap_suppressed_new_terms_alerts';
|
||||
import { bulkCreateSuppressedNewTermsAlertsInMemory } from './bulk_create_suppressed_alerts_in_memory';
|
||||
import type { EventsAndTerms } from './types';
|
||||
import type {
|
||||
|
@ -98,14 +97,13 @@ export const createNewTermsAlertType = (
|
|||
producer: SERVER_APP_ID,
|
||||
solution: 'security',
|
||||
async executor(execOptions) {
|
||||
const { sharedParams, services, params, spaceId, state } = execOptions;
|
||||
const { sharedParams, services, params, state } = execOptions;
|
||||
|
||||
const {
|
||||
ruleExecutionLogger,
|
||||
bulkCreate,
|
||||
completeRule,
|
||||
tuple,
|
||||
mergeStrategy,
|
||||
inputIndex,
|
||||
runtimeMappings,
|
||||
primaryTimestamp,
|
||||
|
@ -113,9 +111,6 @@ export const createNewTermsAlertType = (
|
|||
aggregatableTimestampField,
|
||||
exceptionFilter,
|
||||
unprocessedExceptions,
|
||||
alertTimestampOverride,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
} = sharedParams;
|
||||
|
||||
const isLoggedRequestsEnabled = Boolean(state?.isLoggedRequestsEnabled);
|
||||
|
@ -222,34 +217,6 @@ export const createNewTermsAlertType = (
|
|||
const bucketsForField = searchResultWithAggs.aggregations.new_terms.buckets;
|
||||
|
||||
const createAlertsHook: CreateAlertsHook = async (aggResult) => {
|
||||
const wrapHits = (eventsAndTerms: EventsAndTerms[]) =>
|
||||
wrapNewTermsAlerts({
|
||||
eventsAndTerms,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery: inputIndex,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
const wrapSuppressedHits = (eventsAndTerms: EventsAndTerms[]) =>
|
||||
wrapSuppressedNewTermsAlerts({
|
||||
eventsAndTerms,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery: inputIndex,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
const eventsAndTerms: EventsAndTerms[] = (
|
||||
aggResult?.aggregations?.new_terms.buckets ?? []
|
||||
).map((bucket) => {
|
||||
|
@ -286,14 +253,15 @@ export const createNewTermsAlertType = (
|
|||
sharedParams,
|
||||
eventsAndTerms: eventAndTermsChunk,
|
||||
toReturn: result,
|
||||
wrapHits,
|
||||
services,
|
||||
alertSuppression: params.alertSuppression,
|
||||
wrapSuppressedHits,
|
||||
experimentalFeatures,
|
||||
});
|
||||
} else {
|
||||
const wrappedAlerts = wrapHits(eventAndTermsChunk);
|
||||
const wrappedAlerts = wrapNewTermsAlerts({
|
||||
sharedParams,
|
||||
eventsAndTerms: eventAndTermsChunk,
|
||||
});
|
||||
|
||||
bulkCreateResult = await bulkCreate(
|
||||
wrappedAlerts,
|
||||
|
|
|
@ -7,29 +7,26 @@
|
|||
|
||||
import { ALERT_URL, ALERT_UUID } from '@kbn/rule-data-utils';
|
||||
import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names';
|
||||
import { getCompleteRuleMock, getNewTermsRuleParams } from '../../rule_schema/mocks';
|
||||
import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
|
||||
import { getNewTermsRuleParams } from '../../rule_schema/mocks';
|
||||
import { sampleDocNoSortIdWithTimestamp } from '../__mocks__/es_results';
|
||||
import { wrapNewTermsAlerts } from './wrap_new_terms_alerts';
|
||||
|
||||
const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
|
||||
import { getSharedParamsMock } from '../__mocks__/shared_params';
|
||||
|
||||
const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
|
||||
const publicBaseUrl = 'http://somekibanabaseurl.com';
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams(),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
},
|
||||
});
|
||||
describe('wrapNewTermsAlerts', () => {
|
||||
test('should create an alert with the correct _id from a document', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const completeRule = getCompleteRuleMock(getNewTermsRuleParams());
|
||||
const alerts = wrapNewTermsAlerts({
|
||||
sharedParams,
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }],
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('a36d9fe6fe4b2f65058fb1a487733275f811af58');
|
||||
|
@ -42,17 +39,17 @@ describe('wrapNewTermsAlerts', () => {
|
|||
|
||||
test('should create an alert with a different _id if the space is different', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const completeRule = getCompleteRuleMock(getNewTermsRuleParams());
|
||||
const newSharedParams = getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams(),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
spaceId: 'otherSpace',
|
||||
},
|
||||
});
|
||||
const alerts = wrapNewTermsAlerts({
|
||||
sharedParams: newSharedParams,
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }],
|
||||
spaceId: 'otherSpace',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('f7877a31b1cc83373dbc9ba5939ebfab1db66545');
|
||||
|
@ -65,17 +62,17 @@ describe('wrapNewTermsAlerts', () => {
|
|||
|
||||
test('should create an alert with a different _id if the newTerms array is different', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const completeRule = getCompleteRuleMock(getNewTermsRuleParams());
|
||||
const newSharedParams = getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams(),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
spaceId: 'otherSpace',
|
||||
},
|
||||
});
|
||||
const alerts = wrapNewTermsAlerts({
|
||||
sharedParams: newSharedParams,
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.2'] }],
|
||||
spaceId: 'otherSpace',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('75e5a507a4bc48bcd983820c7fd2d9621ff4e2ea');
|
||||
|
@ -88,17 +85,17 @@ describe('wrapNewTermsAlerts', () => {
|
|||
|
||||
test('should create an alert with a different _id if the newTerms array contains multiple terms', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const completeRule = getCompleteRuleMock(getNewTermsRuleParams());
|
||||
const newSharedParams = getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams(),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
spaceId: 'otherSpace',
|
||||
},
|
||||
});
|
||||
const alerts = wrapNewTermsAlerts({
|
||||
sharedParams: newSharedParams,
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1', '127.0.0.2'] }],
|
||||
spaceId: 'otherSpace',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('86a216cfa4884767d9bb26d2b8db911cb4aa85ce');
|
||||
|
|
|
@ -13,11 +13,8 @@ import type {
|
|||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { CompleteRule, RuleParams } from '../../rule_schema';
|
||||
import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
|
||||
import type { SignalSource } from '../types';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import type { SecuritySharedParams, SignalSource } from '../types';
|
||||
import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
|
||||
|
||||
export interface EventsAndTerms {
|
||||
|
@ -26,49 +23,26 @@ export interface EventsAndTerms {
|
|||
}
|
||||
|
||||
export const wrapNewTermsAlerts = ({
|
||||
sharedParams,
|
||||
eventsAndTerms,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams;
|
||||
eventsAndTerms: EventsAndTerms[];
|
||||
spaceId: string | null | undefined;
|
||||
completeRule: CompleteRule<RuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
indicesToQuery: string[];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}): Array<WrappedFieldsLatest<NewTermsFieldsLatest>> => {
|
||||
return eventsAndTerms.map((eventAndTerms) => {
|
||||
const id = objectHash([
|
||||
eventAndTerms.event._index,
|
||||
eventAndTerms.event._id,
|
||||
String(eventAndTerms.event._version),
|
||||
`${spaceId}:${completeRule.alertId}`,
|
||||
`${sharedParams.spaceId}:${sharedParams.completeRule.alertId}`,
|
||||
eventAndTerms.newTerms,
|
||||
]);
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: eventAndTerms.event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageForNewTermsAlert,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import {
|
||||
ALERT_URL,
|
||||
|
@ -17,11 +16,9 @@ import {
|
|||
} from '@kbn/rule-data-utils';
|
||||
import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names';
|
||||
import { getCompleteRuleMock, getNewTermsRuleParams } from '../../rule_schema/mocks';
|
||||
import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
|
||||
import { sampleDocNoSortIdWithTimestamp } from '../__mocks__/es_results';
|
||||
import { wrapSuppressedNewTermsAlerts } from './wrap_suppressed_new_terms_alerts';
|
||||
|
||||
const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
|
||||
import { getSharedParamsMock } from '../__mocks__/shared_params';
|
||||
|
||||
const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71';
|
||||
const publicBaseUrl = 'http://somekibanabaseurl.com';
|
||||
|
@ -33,20 +30,20 @@ const alertSuppression = {
|
|||
const completeRule = getCompleteRuleMock(getNewTermsRuleParams());
|
||||
completeRule.ruleParams.alertSuppression = alertSuppression;
|
||||
|
||||
const sharedParams = getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams({ alertSuppression }),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
},
|
||||
});
|
||||
|
||||
describe('wrapSuppressedNewTermsAlerts', () => {
|
||||
test('should create an alert with the correct _id from a document and suppression fields', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedNewTermsAlerts({
|
||||
sharedParams,
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }],
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('a36d9fe6fe4b2f65058fb1a487733275f811af58');
|
||||
|
@ -67,22 +64,20 @@ describe('wrapSuppressedNewTermsAlerts', () => {
|
|||
});
|
||||
|
||||
test('should create an alert with a different _id if suppression field is different', () => {
|
||||
const completeRuleCloned = cloneDeep(completeRule);
|
||||
completeRuleCloned.ruleParams.alertSuppression = {
|
||||
groupBy: ['someKey'],
|
||||
};
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedNewTermsAlerts({
|
||||
sharedParams: getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams({
|
||||
alertSuppression: {
|
||||
groupBy: ['someKey'],
|
||||
},
|
||||
}),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
},
|
||||
}),
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }],
|
||||
spaceId: 'default',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule: completeRuleCloned,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('a36d9fe6fe4b2f65058fb1a487733275f811af58');
|
||||
|
@ -102,16 +97,15 @@ describe('wrapSuppressedNewTermsAlerts', () => {
|
|||
test('should create an alert with a different _id if the space is different', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedNewTermsAlerts({
|
||||
sharedParams: getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams({ alertSuppression }),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
spaceId: 'otherSpace',
|
||||
},
|
||||
}),
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }],
|
||||
spaceId: 'otherSpace',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('f7877a31b1cc83373dbc9ba5939ebfab1db66545');
|
||||
|
@ -124,16 +118,15 @@ describe('wrapSuppressedNewTermsAlerts', () => {
|
|||
test('should create an alert with a different _id if the newTerms array is different', () => {
|
||||
const doc = sampleDocNoSortIdWithTimestamp(docId);
|
||||
const alerts = wrapSuppressedNewTermsAlerts({
|
||||
sharedParams: getSharedParamsMock({
|
||||
ruleParams: getNewTermsRuleParams({ alertSuppression }),
|
||||
rewrites: {
|
||||
publicBaseUrl,
|
||||
inputIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
spaceId: 'otherSpace',
|
||||
},
|
||||
}),
|
||||
eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.2'] }],
|
||||
spaceId: 'otherSpace',
|
||||
mergeStrategy: 'missingFields',
|
||||
completeRule,
|
||||
indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp: '@timestamp',
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
|
||||
expect(alerts[0]._id).toEqual('75e5a507a4bc48bcd983820c7fd2d9621ff4e2ea');
|
||||
|
|
|
@ -15,13 +15,11 @@ import type {
|
|||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { CompleteRule, NewTermsRuleParams } from '../../rule_schema';
|
||||
import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters';
|
||||
import { getSuppressionAlertFields, getSuppressionTerms } from '../utils';
|
||||
import type { SignalSource } from '../types';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import type { SecuritySharedParams, SignalSource } from '../types';
|
||||
import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
|
||||
import type { NewTermsRuleParams } from '../../rule_schema';
|
||||
|
||||
export interface EventsAndTerms {
|
||||
event: estypes.SearchHit<SignalSource>;
|
||||
|
@ -29,32 +27,15 @@ export interface EventsAndTerms {
|
|||
}
|
||||
|
||||
export const wrapSuppressedNewTermsAlerts = ({
|
||||
sharedParams,
|
||||
eventsAndTerms,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
intendedTimestamp,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams<NewTermsRuleParams>;
|
||||
eventsAndTerms: EventsAndTerms[];
|
||||
spaceId: string | null | undefined;
|
||||
completeRule: CompleteRule<NewTermsRuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
indicesToQuery: string[];
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
primaryTimestamp: string;
|
||||
secondaryTimestamp?: string;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}): Array<WrappedFieldsLatest<NewTermsFieldsLatest & SuppressionFieldsLatest>> => {
|
||||
return eventsAndTerms.map((eventAndTerms) => {
|
||||
const event = eventAndTerms.event;
|
||||
const { completeRule, spaceId } = sharedParams;
|
||||
|
||||
const suppressionTerms = getSuppressionTerms({
|
||||
alertSuppression: completeRule?.ruleParams?.alertSuppression,
|
||||
|
@ -72,20 +53,11 @@ export const wrapSuppressedNewTermsAlerts = ({
|
|||
]);
|
||||
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage: buildReasonMessageForNewTermsAlert,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -95,8 +67,8 @@ export const wrapSuppressedNewTermsAlerts = ({
|
|||
...baseAlert,
|
||||
[ALERT_NEW_TERMS]: eventAndTerms.newTerms,
|
||||
...getSuppressionAlertFields({
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
primaryTimestamp: sharedParams.primaryTimestamp,
|
||||
secondaryTimestamp: sharedParams.secondaryTimestamp,
|
||||
fields: event.fields,
|
||||
suppressionTerms,
|
||||
fallbackTimestamp: baseAlert[TIMESTAMP],
|
||||
|
|
|
@ -267,16 +267,9 @@ export const groupAndBulkCreate = async ({
|
|||
}));
|
||||
|
||||
const wrappedAlerts = wrapSuppressedAlerts({
|
||||
sharedParams,
|
||||
suppressionBuckets,
|
||||
spaceId: sharedParams.spaceId,
|
||||
completeRule: sharedParams.completeRule,
|
||||
mergeStrategy: sharedParams.mergeStrategy,
|
||||
indicesToQuery: sharedParams.inputIndex,
|
||||
publicBaseUrl: sharedParams.publicBaseUrl,
|
||||
buildReasonMessage,
|
||||
alertTimestampOverride: sharedParams.alertTimestampOverride,
|
||||
ruleExecutionLogger: sharedParams.ruleExecutionLogger,
|
||||
intendedTimestamp: sharedParams.intendedTimestamp,
|
||||
});
|
||||
|
||||
const suppressionDuration = sharedParams.completeRule.ruleParams.alertSuppression?.duration;
|
||||
|
|
|
@ -19,10 +19,7 @@ import type {
|
|||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { ConfigType } from '../../../../../config';
|
||||
import type { CompleteRule, RuleParams } from '../../../rule_schema';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../../rule_monitoring';
|
||||
import type { SignalSource } from '../../types';
|
||||
import type { SecuritySharedParams, SignalSource } from '../../types';
|
||||
import { transformHitToAlert } from '../../factories/utils/transform_hit_to_alert';
|
||||
import type { BuildReasonMessage } from '../../utils/reason_formatters';
|
||||
|
||||
|
@ -47,28 +44,15 @@ export const createSuppressedAlertInstanceId = ({
|
|||
};
|
||||
|
||||
export const wrapSuppressedAlerts = ({
|
||||
sharedParams,
|
||||
suppressionBuckets,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery,
|
||||
buildReasonMessage,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams;
|
||||
suppressionBuckets: SuppressionBucket[];
|
||||
spaceId: string;
|
||||
completeRule: CompleteRule<RuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
indicesToQuery: string[];
|
||||
buildReasonMessage: BuildReasonMessage;
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}): Array<WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest>> => {
|
||||
const { completeRule, spaceId } = sharedParams;
|
||||
return suppressionBuckets.map((bucket) => {
|
||||
const id = objectHash([
|
||||
bucket.event._index,
|
||||
|
@ -85,20 +69,11 @@ export const wrapSuppressedAlerts = ({
|
|||
spaceId,
|
||||
});
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: bucket.event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -29,8 +29,6 @@ interface BulkCreateSuppressedThresholdAlertsParams {
|
|||
buckets: ThresholdBucket[];
|
||||
services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>;
|
||||
startedAt: Date;
|
||||
from: Date;
|
||||
to: Date;
|
||||
experimentalFeatures: ExperimentalFeatures;
|
||||
}
|
||||
|
||||
|
@ -44,24 +42,12 @@ export const bulkCreateSuppressedThresholdAlerts = async ({
|
|||
buckets,
|
||||
services,
|
||||
startedAt,
|
||||
from,
|
||||
to,
|
||||
experimentalFeatures,
|
||||
}: BulkCreateSuppressedThresholdAlertsParams): Promise<{
|
||||
bulkCreateResult: GenericBulkCreateResponse<BaseFieldsLatest & SuppressionFieldsLatest>;
|
||||
unsuppressedAlerts: Array<SearchHit<unknown>>;
|
||||
}> => {
|
||||
const {
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
inputIndex,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
} = sharedParams;
|
||||
const ruleParams = completeRule.ruleParams;
|
||||
const suppressionDuration = completeRule.ruleParams.alertSuppression?.duration;
|
||||
const suppressionDuration = sharedParams.completeRule.ruleParams.alertSuppression?.duration;
|
||||
if (!suppressionDuration) {
|
||||
throw Error('Suppression duration can not be empty');
|
||||
}
|
||||
|
@ -69,22 +55,10 @@ export const bulkCreateSuppressedThresholdAlerts = async ({
|
|||
const suppressionWindow = `now-${suppressionDuration.value}${suppressionDuration.unit}`;
|
||||
|
||||
const wrappedAlerts = wrapSuppressedThresholdALerts({
|
||||
sharedParams,
|
||||
buckets,
|
||||
spaceId: sharedParams.spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery: inputIndex,
|
||||
buildReasonMessage: buildReasonMessageForThresholdAlert,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
inputIndex: inputIndex.join(','),
|
||||
startedAt,
|
||||
from,
|
||||
to,
|
||||
suppressionWindow,
|
||||
threshold: ruleParams.threshold,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
const bulkCreateResult = await bulkCreateWithSuppression({
|
||||
|
|
|
@ -16,26 +16,18 @@ import type { ThresholdNormalized } from '../../../../../common/api/detection_en
|
|||
import type { GenericBulkCreateResponse } from '../factories/bulk_create_factory';
|
||||
import { calculateThresholdSignalUuid } from './utils';
|
||||
import { buildReasonMessageForThresholdAlert } from '../utils/reason_formatters';
|
||||
import type { ThresholdSignalHistory, ThresholdBucket } from './types';
|
||||
import type { BulkCreate, WrapHits } from '../types';
|
||||
import type { CompleteRule, ThresholdRuleParams } from '../../rule_schema';
|
||||
import type { ThresholdBucket } from './types';
|
||||
import type { SecuritySharedParams } from '../types';
|
||||
import type { ThresholdRuleParams } from '../../rule_schema';
|
||||
import type { BaseFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import { createEnrichEventsFunction } from '../utils/enrichments';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { wrapHits } from '../factories';
|
||||
|
||||
interface BulkCreateThresholdSignalsParams {
|
||||
sharedParams: SecuritySharedParams<ThresholdRuleParams>;
|
||||
buckets: ThresholdBucket[];
|
||||
completeRule: CompleteRule<ThresholdRuleParams>;
|
||||
services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>;
|
||||
inputIndexPattern: string[];
|
||||
filter: unknown;
|
||||
signalsIndex: string;
|
||||
startedAt: Date;
|
||||
from: Date;
|
||||
signalHistory: ThresholdSignalHistory;
|
||||
bulkCreate: BulkCreate;
|
||||
wrapHits: WrapHits;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
}
|
||||
|
||||
export const transformBucketIntoHit = (
|
||||
|
@ -90,25 +82,28 @@ export const getTransformedHits = (
|
|||
return transformBucketIntoHit(bucket, inputIndex, startedAt, from, threshold, ruleId);
|
||||
});
|
||||
|
||||
export const bulkCreateThresholdSignals = async (
|
||||
params: BulkCreateThresholdSignalsParams
|
||||
): Promise<GenericBulkCreateResponse<BaseFieldsLatest>> => {
|
||||
const ruleParams = params.completeRule.ruleParams;
|
||||
export const bulkCreateThresholdSignals = async ({
|
||||
sharedParams,
|
||||
buckets,
|
||||
services,
|
||||
startedAt,
|
||||
}: BulkCreateThresholdSignalsParams): Promise<GenericBulkCreateResponse<BaseFieldsLatest>> => {
|
||||
const ruleParams = sharedParams.completeRule.ruleParams;
|
||||
const ecsResults = getTransformedHits(
|
||||
params.buckets,
|
||||
params.inputIndexPattern.join(','),
|
||||
params.startedAt,
|
||||
params.from,
|
||||
buckets,
|
||||
sharedParams.inputIndex.join(','),
|
||||
startedAt,
|
||||
sharedParams.tuple.from.toDate(),
|
||||
ruleParams.threshold,
|
||||
ruleParams.ruleId
|
||||
);
|
||||
|
||||
return params.bulkCreate(
|
||||
params.wrapHits(ecsResults, buildReasonMessageForThresholdAlert),
|
||||
return sharedParams.bulkCreate(
|
||||
wrapHits(sharedParams, ecsResults, buildReasonMessageForThresholdAlert),
|
||||
undefined,
|
||||
createEnrichEventsFunction({
|
||||
services: params.services,
|
||||
logger: params.ruleExecutionLogger,
|
||||
services,
|
||||
logger: sharedParams.ruleExecutionLogger,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -69,8 +69,6 @@ export const thresholdExecutor = async ({
|
|||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
runtimeMappings,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
} = sharedParams;
|
||||
const result = createSearchAfterReturnType();
|
||||
const ruleParams = completeRule.ruleParams;
|
||||
|
@ -146,8 +144,6 @@ export const thresholdExecutor = async ({
|
|||
buckets,
|
||||
services,
|
||||
startedAt,
|
||||
from: tuple.from.toDate(),
|
||||
to: tuple.to.toDate(),
|
||||
experimentalFeatures,
|
||||
});
|
||||
const createResult = suppressedResults.bulkCreateResult;
|
||||
|
@ -161,18 +157,10 @@ export const thresholdExecutor = async ({
|
|||
});
|
||||
} else {
|
||||
const createResult = await bulkCreateThresholdSignals({
|
||||
sharedParams,
|
||||
buckets,
|
||||
completeRule,
|
||||
filter: esFilter,
|
||||
services,
|
||||
inputIndexPattern: inputIndex,
|
||||
signalsIndex: ruleParams.outputIndex,
|
||||
startedAt,
|
||||
from: tuple.from.toDate(),
|
||||
signalHistory: validSignalHistory,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
ruleExecutionLogger,
|
||||
});
|
||||
|
||||
newSignalHistory = buildThresholdSignalHistory({
|
||||
|
|
|
@ -22,15 +22,13 @@ import type {
|
|||
BaseFieldsLatest,
|
||||
WrappedFieldsLatest,
|
||||
} from '../../../../../common/api/detection_engine/model/alerts';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import type { CompleteRule, ThresholdRuleParams } from '../../rule_schema';
|
||||
import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring';
|
||||
import { transformHitToAlert } from '../factories/utils/transform_hit_to_alert';
|
||||
|
||||
import type { ThresholdBucket } from './types';
|
||||
import type { BuildReasonMessage } from '../utils/reason_formatters';
|
||||
import { transformBucketIntoHit } from './bulk_create_threshold_signals';
|
||||
import type { ThresholdNormalized } from '../../../../../common/api/detection_engine/model/rule_schema';
|
||||
import type { SecuritySharedParams } from '../types';
|
||||
import type { ThresholdRuleParams } from '../../rule_schema';
|
||||
|
||||
/**
|
||||
* wraps suppressed threshold alerts
|
||||
|
@ -39,46 +37,24 @@ import type { ThresholdNormalized } from '../../../../../common/api/detection_en
|
|||
* populates alert's suppression fields
|
||||
*/
|
||||
export const wrapSuppressedThresholdALerts = ({
|
||||
sharedParams,
|
||||
buckets,
|
||||
spaceId,
|
||||
completeRule,
|
||||
mergeStrategy,
|
||||
indicesToQuery,
|
||||
buildReasonMessage,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
inputIndex,
|
||||
startedAt,
|
||||
from,
|
||||
to,
|
||||
threshold,
|
||||
intendedTimestamp,
|
||||
}: {
|
||||
sharedParams: SecuritySharedParams<ThresholdRuleParams>;
|
||||
buckets: ThresholdBucket[];
|
||||
spaceId: string;
|
||||
completeRule: CompleteRule<ThresholdRuleParams>;
|
||||
mergeStrategy: ConfigType['alertMergeStrategy'];
|
||||
indicesToQuery: string[];
|
||||
buildReasonMessage: BuildReasonMessage;
|
||||
alertTimestampOverride: Date | undefined;
|
||||
ruleExecutionLogger: IRuleExecutionLogForExecutors;
|
||||
publicBaseUrl: string | undefined;
|
||||
inputIndex: string;
|
||||
startedAt: Date;
|
||||
from: Date;
|
||||
to: Date;
|
||||
suppressionWindow: string;
|
||||
threshold: ThresholdNormalized;
|
||||
intendedTimestamp: Date | undefined;
|
||||
}): Array<WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest>> => {
|
||||
const { completeRule, spaceId } = sharedParams;
|
||||
return buckets.map((bucket) => {
|
||||
const hit = transformBucketIntoHit(
|
||||
bucket,
|
||||
inputIndex,
|
||||
sharedParams.inputIndex.join(','),
|
||||
startedAt,
|
||||
from,
|
||||
threshold,
|
||||
sharedParams.tuple.from.toDate(),
|
||||
completeRule.ruleParams.threshold,
|
||||
completeRule.ruleParams.ruleId
|
||||
);
|
||||
|
||||
|
@ -89,20 +65,11 @@ export const wrapSuppressedThresholdALerts = ({
|
|||
const instanceId = objectHash([suppressedValues, completeRule.alertId, spaceId]);
|
||||
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: hit,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -89,7 +89,6 @@ export interface SecuritySharedParams<TParams extends RuleParams = RuleParams> {
|
|||
listClient: ListClient;
|
||||
searchAfterSize: number;
|
||||
bulkCreate: BulkCreate;
|
||||
wrapHits: WrapHits;
|
||||
ruleDataClient: IRuleDataClient;
|
||||
inputIndex: string[];
|
||||
runtimeMappings: estypes.MappingRuntimeFields | undefined;
|
||||
|
@ -106,6 +105,8 @@ export interface SecuritySharedParams<TParams extends RuleParams = RuleParams> {
|
|||
experimentalFeatures?: ExperimentalFeatures;
|
||||
intendedTimestamp: Date | undefined;
|
||||
spaceId: string;
|
||||
ignoreFields: Record<string, boolean>;
|
||||
ignoreFieldsRegexes: string[];
|
||||
}
|
||||
|
||||
export type SecurityAlertType<
|
||||
|
@ -340,11 +341,6 @@ export type BulkCreate = <T extends BaseFieldsLatest>(
|
|||
|
||||
export type SimpleHit = BaseHit<{ '@timestamp'?: string }>;
|
||||
|
||||
export type WrapHits = (
|
||||
hits: Array<estypes.SearchHit<SignalSource>>,
|
||||
buildReasonMessage: BuildReasonMessage
|
||||
) => Array<WrappedFieldsLatest<BaseFieldsLatest>>;
|
||||
|
||||
export type WrapSuppressedHits = (
|
||||
hits: Array<estypes.SearchHit<SignalSource>>,
|
||||
buildReasonMessage: BuildReasonMessage
|
||||
|
|
|
@ -35,6 +35,7 @@ import type {
|
|||
import { robustGet } from './source_fields_merging/utils/robust_field_access';
|
||||
import { buildAlertGroupFromSequence } from '../eql/build_alert_group_from_sequence';
|
||||
import type { EqlRuleParams } from '../../rule_schema';
|
||||
import { wrapHits } from '../factories';
|
||||
|
||||
interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndBulkCreateParams {
|
||||
wrapSuppressedHits: WrapSuppressedHits;
|
||||
|
@ -97,7 +98,7 @@ export const bulkCreateSuppressedAlertsInMemory = async ({
|
|||
mergeSourceAndFields
|
||||
);
|
||||
|
||||
unsuppressibleWrappedDocs = sharedParams.wrapHits(partitionedEvents[1], buildReasonMessage);
|
||||
unsuppressibleWrappedDocs = wrapHits(sharedParams, partitionedEvents[1], buildReasonMessage);
|
||||
suppressibleEvents = partitionedEvents[0];
|
||||
}
|
||||
|
||||
|
|
|
@ -39,13 +39,11 @@ import {
|
|||
SPACE_IDS,
|
||||
TIMESTAMP,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import type { BulkCreate, BulkResponse, WrapHits } from '../types';
|
||||
import { getCompleteRuleMock, getQueryRuleParams } from '../../rule_schema/mocks';
|
||||
import type { BulkCreate, BulkResponse } from '../types';
|
||||
import { getQueryRuleParams } from '../../rule_schema/mocks';
|
||||
import { bulkCreateFactory } from '../factories/bulk_create_factory';
|
||||
import { wrapHitsFactory } from '../factories/wrap_hits_factory';
|
||||
import { ruleExecutionLogMock } from '../../rule_monitoring/mocks';
|
||||
import type { BuildReasonMessage } from './reason_formatters';
|
||||
import type { QueryRuleParams } from '../../rule_schema';
|
||||
import { SERVER_APP_ID } from '../../../../../common/constants';
|
||||
import { getSharedParamsMock } from '../__mocks__/shared_params';
|
||||
|
||||
|
@ -60,20 +58,7 @@ describe('searchAfterAndBulkCreate', () => {
|
|||
const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create();
|
||||
const someGuids = Array.from({ length: 13 }).map(() => uuidv4());
|
||||
const sampleParams = getQueryRuleParams();
|
||||
const queryCompleteRule = getCompleteRuleMock<QueryRuleParams>(sampleParams);
|
||||
const inputIndex = ['auditbeat-*'];
|
||||
const wrapHits: WrapHits = wrapHitsFactory({
|
||||
completeRule: queryCompleteRule,
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
spaceId: 'default',
|
||||
indicesToQuery: inputIndex,
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl: 'http://testkibanabaseurl.com',
|
||||
intendedTimestamp: undefined,
|
||||
});
|
||||
const bulkCreate: BulkCreate = bulkCreateFactory(
|
||||
mockPersistenceServices.alertWithPersistence,
|
||||
false,
|
||||
|
@ -102,7 +87,6 @@ describe('searchAfterAndBulkCreate', () => {
|
|||
rewrites: {
|
||||
inputIndex,
|
||||
bulkCreate,
|
||||
wrapHits,
|
||||
searchAfterSize: 1,
|
||||
listClient,
|
||||
},
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType
|
|||
import { createEnrichEventsFunction } from './enrichments';
|
||||
import type { SearchAfterAndBulkCreateFactoryParams } from './search_after_bulk_create_factory';
|
||||
import { searchAfterAndBulkCreateFactory } from './search_after_bulk_create_factory';
|
||||
import { wrapHits } from '../factories';
|
||||
|
||||
// search_after through documents and re-index using bulk endpoint.
|
||||
export const searchAfterAndBulkCreate = async (
|
||||
|
@ -21,7 +22,7 @@ export const searchAfterAndBulkCreate = async (
|
|||
enrichedEvents,
|
||||
toReturn,
|
||||
}) => {
|
||||
const wrappedDocs = sharedParams.wrapHits(enrichedEvents, buildReasonMessage);
|
||||
const wrappedDocs = wrapHits(sharedParams, enrichedEvents, buildReasonMessage);
|
||||
|
||||
const bulkCreateResult = await sharedParams.bulkCreate(
|
||||
wrappedDocs,
|
||||
|
|
|
@ -148,8 +148,7 @@ describe('wrapSuppressedAlerts', () => {
|
|||
});
|
||||
|
||||
expect(transformHitToAlertMock).toHaveBeenCalledWith({
|
||||
spaceId: 'default',
|
||||
completeRule: sharedParams.completeRule,
|
||||
sharedParams,
|
||||
doc: {
|
||||
fields: {
|
||||
'@timestamp': [expectedTimestamp],
|
||||
|
@ -159,16 +158,9 @@ describe('wrapSuppressedAlerts', () => {
|
|||
_id: '1',
|
||||
_index: 'test*',
|
||||
},
|
||||
mergeStrategy: 'missingFields',
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
indicesToQuery: ['test*'],
|
||||
alertTimestampOverride: undefined,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: expect.any(String),
|
||||
publicBaseUrl: 'public-url-mock',
|
||||
});
|
||||
expect(wrappedAlerts[0]._source).toEqual(
|
||||
expect.objectContaining({
|
||||
|
|
|
@ -36,18 +36,7 @@ export const wrapSuppressedAlerts = ({
|
|||
buildReasonMessage: BuildReasonMessage;
|
||||
sharedParams: SecuritySharedParams<MachineLearningRuleParams | EqlRuleParams | ThreatRuleParams>;
|
||||
}): Array<WrappedFieldsLatest<BaseFieldsLatest & SuppressionFieldsLatest>> => {
|
||||
const {
|
||||
completeRule,
|
||||
spaceId,
|
||||
mergeStrategy,
|
||||
inputIndex: indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
primaryTimestamp,
|
||||
secondaryTimestamp,
|
||||
} = sharedParams;
|
||||
const { completeRule, spaceId, primaryTimestamp, secondaryTimestamp } = sharedParams;
|
||||
return events.map((event) => {
|
||||
const suppressionTerms = getSuppressionTerms({
|
||||
alertSuppression: completeRule?.ruleParams?.alertSuppression,
|
||||
|
@ -65,20 +54,11 @@ export const wrapSuppressedAlerts = ({
|
|||
const instanceId = objectHash([suppressionTerms, completeRule.alertId, spaceId]);
|
||||
|
||||
const baseAlert: BaseFieldsLatest = transformHitToAlert({
|
||||
spaceId,
|
||||
completeRule,
|
||||
sharedParams,
|
||||
doc: event,
|
||||
mergeStrategy,
|
||||
ignoreFields: {},
|
||||
ignoreFieldsRegexes: [],
|
||||
applyOverrides: true,
|
||||
buildReasonMessage,
|
||||
indicesToQuery,
|
||||
alertTimestampOverride,
|
||||
ruleExecutionLogger,
|
||||
alertUuid: id,
|
||||
publicBaseUrl,
|
||||
intendedTimestamp,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -158,7 +158,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
'kibana.alert.rule.from': '2020-10-28T06:00:00.000Z',
|
||||
'kibana.alert.rule.immutable': false,
|
||||
'kibana.alert.rule.interval': '1h',
|
||||
'kibana.alert.rule.indices': [],
|
||||
'kibana.alert.rule.indices': ['ecs_compliant'],
|
||||
'kibana.alert.rule.max_signals': 100,
|
||||
'kibana.alert.rule.references': [],
|
||||
'kibana.alert.rule.risk_score_mapping': [],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue